代码随想录 LeetCode数组篇 移除元素

news2024/9/22 19:33:56

文章目录

  • 27. 移除元素
  • 26. 删除有序数组中的重复项
  • 283. 移动零
  • 844. 比较含退格的字符串
  • 977. 有序数组的平方


27. 移除元素

在这里插入图片描述
我的思路:

简单来说,将要删除的元素放到数组的最后

当数组中的元素和val的值相同时,就和数组末尾的值进行交换。
所以,需要一个变量last指向数组的末尾。当last的位置已经放了和val相等的数值时,last向前移一位,即last–

在提交的过程中,发现还有一些特殊情况,比如:

测试用例1:
[]
0
测试用例2:
[1]
1
测试用例3
[0, 4, 4, 0, 4, 4, 4, 0, 2]
4

在这里插入图片描述

class Solution {
    public int removeElement(int[] nums, int val) {
        if (nums.length == 0) {
            return 0;
        }

        int last = nums.length - 1;
        while (last >= 0 && nums[last] == val) {
            last--;
        }
        for (int i = 0; i <= last; i++) {
            if (nums[i] != val) {
                continue;
            }
            while (last >= 0 && nums[last] == val) {
                last--;
            }
            if (last + 1 == i) {
                break;
            }
            int tmp = nums[last];
            nums[last] = nums[i];
            nums[i] = tmp;
            last--;
        }
        return last + 1;
    }
}

官方解答,方式一:双指针

分析,题目要求删除数组中等于val的元素,因此输出数组的长度一定小于等于输入数组的长度,而且,题目要求不使用额外的数组空间,那么就可以把数组直接写在输入数组上。

可以使用双指针:右指针right指向当前将要处理的元素,左指针left指向下一个将要赋值的位置。

  • 如果右指针指向的元素不等于val,那么,它一定是输出数组中的元素,就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移
  • 如果右指针指向的元素等于val,那么,它就不能在输出数组里,此时左指针不动,右指针右移一位

最后,在[0,left)中的元素都不等于val。
当右指针遍历完输入数组以后,left的值就是输出数组的长度。

这样的算法,最坏的情况是,输入的数组中没有等于val的值,左右指针各遍历了数组一次。

在这里插入图片描述

class Solution {
    public int removeElement(int[] nums, int val) {
    
        if (nums.length == 0) {
            return 0;
        }

        int left = 0;
        int right = 0;
        while (right < nums.length) {
            if (nums[right] != val) {
                nums[left] = nums[right];
                left++;
            }
            right++;
        }
        return left;
    }
}

官方解答 方法二:双指针优化(和我的思路差不多)

在题目中提到,元素的顺序可以改变,当要移除的元素恰好在数组的开头,可以将数组中的最后一个元素移动到数组的开头,取代要删除的元素。

实现方面,使用双指针,两个指针初始时分别位于数组的首尾,向中间移动遍历该序列。

如果左指针left指向的元素等于val,此时将右指针right指向的元素赋值到左指针left的位置,然后右指针right左移一位。如果赋值过来的元素恰好也等于val,可以继续把右指针指向的元素赋值过来(左指针left指向的等于val的元素的位置继续被覆盖),直到左指针指向的元素的值不等于val为止。

当左指针left和右指针right重合的时候,左右指针遍历完数组中的所有元素。

这样的方法两个指针在最坏的情况下合起来只遍历了数组一次。与上面的方法一不同的是,方法二避免了需要保留的元素的重复赋值操作。

class Solution {
    public int removeElement(int[] nums, int val) {
        if (nums.length == 0) {
            return 0;
        }

        int left = 0;
        int right = nums.length - 1;
        //如果right初始化为nums.length,那么while循环的条件是<,赋值时是nums[left]=nums[right-1]
        //如果哦right初始化为nums.length-1,那么while循环条件是<=,赋值时是nums[left]=nums[right]
        //可以用nums=[1],val=1这个比较特殊的测试用例来思考
        while (left <= right) {
            if (nums[left] == val) {
                nums[left] = nums[right];
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
}

26. 删除有序数组中的重复项

在这里插入图片描述
在这里插入图片描述

我的思路:使用LinkedHashSet去重,并且,LinkedHashSet可以记录添加元素的顺序
在这里插入图片描述

import java.util.LinkedHashSet;

class Solution {
    public int removeDuplicates(int[] nums) {

        LinkedHashSet<Integer> set = new LinkedHashSet<>();
        for (int num : nums) {
            set.add(num);
        }
        int index = 0;
        for (Integer i : set) {
            nums[index] = i;
            index++;
        }
        return set.size();
    }
}

其他思路:双指针

首先,题目中说明了数组是有序的,那么重复的元素一定会相邻。

要求删除重复的元素,实际上就是将不重复的元素移到数组的左侧。

考虑用两个指针,一个在前记作p,一个在后记作q,算法流程如下:

  1. 比较p和q为止的元素是否相等
  2. 相等,q后移一位
  3. 不相等,将q位置的元素复制到p+1位置上,p后移一位,q后移一位
  4. 重复1-3步,直到q等于数组长度
  5. 返回p+1,即为新数组的长度

在这里插入图片描述

class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        int right = 0;
        int n = nums.length;
        while (right < n) {
            if (nums[left] != nums[right]) {
                nums[left + 1] = nums[right];
                left++;
            }
            right++;
        }
        return left + 1;
    }
}

优化:如果数组中没有重复的元素,按照上面的算法流程,每次比较时nums[p]都不等于nums[q],因此,会将q指向的元素原地复制一遍,这个操作其实是不必要的

因此,可以添加一个小判断,当q-p>1时,才进行复制

在这里插入图片描述

class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        int right = 1;
        int n = nums.length;
        while (right < n) {
            if (nums[left] != nums[right]) {
                if (right - left > 1){
                    nums[left+1] = nums[right];
                }
                left++;
            }
            right++;
        }
        return left + 1;
    }
}

283. 移动零

在这里插入图片描述
我的思路:
p指针指向当前要操作的位置,如果当前位置的数值不等于0,那么p后移一位,如果p指向的当前位置为0,那么就需要找出在p后面的不为0的数的位置q,将其值赋值到p的位置,并将q的位置赋值为0

根据题目所给的提示,如果数组的长度是 1 0 4 10^4 104,假设一个比较极端的情况,只有第一个数字是非零,后面的9999个数字都是零,那么,就会做很多无用功,导致执行用时花费很长时间

在这里插入图片描述

class Solution {
    public void moveZeroes(int[] nums) {

        int p = 0;
        int q = 0;
        int n = nums.length;
        while (p < n && q < n) {
            if (nums[p] != 0) {
                p++;
            } else {
                q = p + 1;
                while (q < n && nums[q] == 0) {
                    q++;
                }
                if (q < n) {
                    nums[p] = nums[q];
                    nums[q] = 0;
                }
            }
        }
    }
}

我的另一种思路

在这里插入图片描述

双指针p和q,一开始都指向数组位置0

  • 如果p位置的值为0,那么就要看q位置的值是否为0
    • 如果q位置的值为0,则,q后移一位
    • 如果q位置的值不为0,则将q位置上的值赋值到p的位置上,然后q位置上的值设置为0,p和q都后移一位
  • 如果p位置的值不为0,则,p和q都后移一位
class Solution {
    public void moveZeroes(int[] nums) {

        int p = 0;
        int q = 0;
        int n = nums.length;
        while (p < n && q < n) {
            if (nums[p] == 0) {
                if (nums[q] != 0) {
                    nums[p] = nums[q];
                    nums[q] = 0;
                    p++;
                }
            } else {
                p++;
            }
            q++;
        }
    }
}

其他思路

创建两个指针i和j,第一遍历的时候,指针j用来记录当前有多少非0元素。即遍历的时候每遇到一个非0元素,就将其往数组左边挪,第一次遍历完成后,j指针的下标就指向了最后一个非0元素的下标。

第二次遍历的时候,其实位置从k开始到结束,将剩下的这段区域内的元素全部置为0。

在这里插入图片描述

class Solution {
    public void moveZeroes(int[] nums) {

        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                nums[j++] = nums[i];
            }
        }

        for (int i = j; i < nums.length; i++) {
            nums[i] = 0;
        }
    }
}

844. 比较含退格的字符串

在这里插入图片描述
在这里插入图片描述
我的思路:使用栈来模拟

在这里插入图片描述

import java.util.Stack;

class Solution {
    public boolean backspaceCompare(String s, String t) {
        Stack<Character> ss = new Stack<>();
        Stack<Character> st = new Stack<>();
        char[] charS = s.toCharArray();
        char[] charT = t.toCharArray();
        for (char c : charS) {
            if (c == '#' && !ss.isEmpty()) {
                ss.pop();
            } else if (c != '#') {
                ss.push(c);
            }
        }
        for (char c : charT) {
            if (c == '#' && !st.isEmpty()) {
                st.pop();
            } else if (c != '#') {
                st.push(c);
            }
        }
        if (ss.size() != st.size()) {
            return false;
        }
        while (!ss.isEmpty()) {
            Character c1 = ss.pop();
            Character c2 = st.pop();
            if (!c1.equals(c2)) {
                return false;
            }
        }
        return true;
    }
}

官方思路,方法一:重构字符串

看到这个题目,最容易想到的就是将给定的字符串中的退格符和应当被删除的字符都去除,还原给定字符串的一般形式。然后直接比较两个字符串是否相等即可。

用栈来处理遍历过程,每次遍历一个字符:

  • 如果它是退格符,那么将栈顶弹出
  • 如果是普通字符,将其压入栈中

说是用栈,但是,使用的StringBuilder来模拟栈
在这里插入图片描述

class Solution {
    public boolean backspaceCompare(String s, String t) {
        return build(s).equals(build(t));
    }

    public String build(String str) {
        StringBuilder stringBuilder = new StringBuilder();
        int length = str.length();
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            if (c != '#') {
                stringBuilder.append(c);
            } else {
                if (stringBuilder.length() != 0) {
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                }
            }
        }
        return stringBuilder.toString();
    }
}

复杂度分析:

  • 时间复杂度:O(N+M),其中N和M分别为字符串S和T的长度。因为需要遍历两个字符串各一次
  • 空间复杂度:O(N+M),其中N和M分别是字符串S和T的长度。主要是使用StringBuilder还原出字符串的开销。

官方思路,方法二:双指针

一个字符是否会被删除,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此,可以逆序地遍历字符串,就可以立即确定当前字符是否会被删除掉。

具体地,定义skip表示当前待删除地字符的数量。每次我们遍历到一个字符:

  • 如果该字符为退格符,则需要多删除一个普通字符,skip+1
  • 若该字符是普通字符:
    • 若skip为0,则说明当前字符不需要删去
    • 若skip不为0,则说明当前字符需要删去,skip-1

这样,定义两个指针,分别指向两字符的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或者遍历完字符串为止。

在这里插入图片描述

class Solution {
    public boolean backspaceCompare(String s, String t) {

        int i = s.length() - 1;
        int j = t.length() - 1;

        int skipS = 0;
        int skipT = 0;

        while (i >= 0 || j >= 0) {
            while (i >= 0) {
                char c = s.charAt(i);
                if (c == '#') {
                    skipS++;
                    i--;
                } else if (skipS > 0) {
                    skipS--;
                    i--;
                } else {
                    break;
                }
            }
            while (j >= 0) {
                char c = t.charAt(j);
                if (c == '#') {
                    skipT++;
                    j--;
                } else if (skipT > 0) {
                    skipT--;
                    j--;
                } else {
                    break;
                }
            }
            if (i >= 0 && j >= 0) {
                if (s.charAt(i) != t.charAt(j)) {
                    return false;
                }
            } else {
                if (i >= 0 || j >= 0) {
                    return false;
                }
            }
            i--;
            j--;
        }
        return true;
    }
}

977. 有序数组的平方

在这里插入图片描述

我的思路:因为数组是按照非递减的顺序排列的,所以,数组两端的数的平方大于等于中间数的平方,所以从两头开始比较,然后从后往前向新数组中填数

class Solution {
    public int[] sortedSquares(int[] nums) {
        int[] res = new int[nums.length];
        int left = 0;
        int right = nums.length - 1;
        int i = nums.length - 1;
        while (left <= right) {
            if (nums[left] * nums[left] > nums[right] * nums[right]) {
                res[i] = nums[left] * nums[left];
                left++;
            } else {
                res[i] = nums[right] * nums[right];
                right--;
            }
            i--;
        }
        return res;
    }
}

在这里插入图片描述

复杂度分析:

  • 时间复杂度:O(n),其中n是数组nums的长度
  • 空间复杂度:O(1)。除了存储答案的数组以外,需要维护常量空间。

其他思路,双指针

如果给定数组中的数都是非负数,那么每个数平方后,数组仍然保持升序;如果数组nums中的所有数都是负数,那么将每个数平方后,数组会保持降序。

这样,就可以找到数组nums中负数与非负数的分界线neg,就可以用类似【归并排序】的方法。nums[0]到nums[neg]均为负数,nums[neg+1]到nums[n-1]均为非负数。当将数组nums中的数平方后,那么nums[0]到nums[neg]单调递减,nums[neg+1]到nums[n-1]单调递增。

由于得到了两个已经有序的子数组,因此可以使用归并的方法进行排序。使用两个指针分别指向neg和neg+1,每次比较两个指针对应的数,选择较小的那个放入答案并移动指针。当某一指针移至边界时,将另一指针还未遍历到的数一次放入答案。

在这里插入图片描述

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int neg = -1;
        for (int i = 0; i < n; i++) {
            if (nums[i] < 0) {
                neg = i;
            } else {
                break;
            }
        }

        int[] ans = new int[n];
        int i = neg;
        int j = neg + 1;
        int index = 0;
        while (i >= 0 || j < n) {
            if (i < 0) {
                ans[index] = nums[j] * nums[j];
                j++;
            } else if (j >= n) {
                ans[index] = nums[i] * nums[i];
                i--;
            } else if (nums[i] * nums[i] > nums[j] * nums[j]) {
                ans[index] = nums[j] * nums[j];
                j++;
            } else {
                ans[index] = nums[i] * nums[i];
                i--;
            }
            index++;
        }
        return ans;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/494245.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE进阶】——第五节.SpringMVC学习介绍(下)(获取Cookies、Session和Header、IDEA热部署)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;JavaEE进阶 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录 前言…

​射频PCB 设计​的六大条技巧

即使是最自信的设计人员&#xff0c;对于射频电路也往往望而却步&#xff0c;因为它会带来巨大的设计挑战&#xff0c;并且需要专业的设计和分析工具。这里将为您介绍六条技巧&#xff0c;来帮助您简化任何射频PCB 设计任务和减轻工作压力&#xff01; 1、保持完好、精确的射频…

如何将 Elasticsearch 和时间序列数据流用于可观察性指标 - 8.7

作者&#xff1a;Nicolas Ruflin Elasticsearch 用于多种数据类型 —— 其中之一就是指标。 随着多年前 Metricbeat 的推出以及后来我们的 APM 代理的推出&#xff0c;指标用例变得更加流行。 多年来&#xff0c;Elasticsearch 在如何处理指标聚合和稀疏文档等方面做出了许多改…

ES6-Class类

ES6 提供了更接近传统语言的写法&#xff0c;引入了 Class &#xff08;类&#xff09;这个概念&#xff0c;作为对 象的模板。通过 class 关键字&#xff0c;可以定义类。基本上&#xff0c; ES6 的 class 可以看作只是 一个语法糖&#xff0c;它的绝大部分功能&…

低代码开发重要工具:jvs-logic逻辑引擎的循环处理配置

循环操作是我们常常所见的业务处理方式&#xff0c;那么我们需要如何配置循环操作呢&#xff0c;我们接下来先看个简单的例子&#xff0c; 如下图所示&#xff0c;在一个列表页上&#xff0c;有个表级按钮&#xff0c;这个按钮是将本列表页的所有 “数量” 都 1 配置的思路通…

PostgreSQL+repmgr高可用部署

REPMGR 是一套在PostgreSQL服务器集群中用于管理复制和故障转移的开源工具 。它支持并增强了PostgreSQL的 内置流式复制&#xff0c;提供单个读/写主服务器 以及一个或多个只读备用数据库&#xff0c;其中包含主数据库的近实时副本服务器的数据库。 它提供了两个主要工具&#…

配电室设备监测怎么办?管理高手都是这样做的!

随着智能电网的不断推进&#xff0c;供配电安全也逐渐进入人们的视野&#xff0c;传统人工巡检的方式与当前智能化配电室的建设显得格格不入。 配电室&#xff0c;作为分配多路低压负荷开关的重要节点&#xff0c;其安全系数不言而喻&#xff0c;在管理和监控方面需要慎之又慎。…

数据结构初阶--链表OJ

目录 前言移除链表元素思路分析代码实现 链表的中间节点思路分析代码实现 反转链表思路分析代码实现 链表分割思路分析代码实现 合并两个有序链表思路分析代码实现 前言 本篇文章将对部分单链表的OJ题进行讲解 移除链表元素 我们先来看题 思路分析 我们可以采用双指针的方…

测试常见概念

文章目录 需求测试用例BUG软件生命周期开发模型scrum测试模型 需求 需求的概念&#xff1a;满足用户期望或正式规定文档(合同、标准、规范)所具有的条件和权能&#xff0c;包含用户需求和软件需求 用户需求&#xff1a;可以简单理解为甲方提出的需求&#xff0c;如果没有甲方&…

java错题总结(19-21页)

链接&#xff1a;关于Java中的ClassLoader下面的哪些描述是错误的_用友笔试题_牛客网 来源&#xff1a;牛客网 B&#xff1a;先讲一下双亲委派机制&#xff0c;简单来说&#xff0c;就是加载一个类的时候&#xff0c;会往上找他的父类加载器&#xff0c;父类加载器找它的父类加…

Netty(1)

Netty 文章目录 Netty1 Netty 基本介绍2 why Netty2.1 原生 NIO 问题2.2 Netty 优点 3 I/O 线程模型3.1 传统阻塞 I/O 模型3.2 Reactor 模式3.2.1 Reactor 模式解决传统 I/O 方案3.2.2 Reactor 模式原理图3.2.3 Reactor 的核心组件3.2.4 单 Readcot 单线程(NIO模型)3.2.5 单 Re…

非科班转码,春招总结!

作者&#xff1a;阿秀 校招八股文学习网站&#xff1a;https://interviewguide.cn 这是阿秀的第「263」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 欢迎今年参加秋招的小伙伴加入阿秀的学习圈&#xff0c;目前已经超过 2300 小伙伴加入&#xff01;去年认真准备和走下来的基…

Twitter 推荐算法底有多牛? 已斩获11.7K star

点击上方“Github中文社区”&#xff0c;关注 看Github&#xff0c;每天提升第070期分享 &#xff0c;作者&#xff1a;Huber | Github中文社区 大家好&#xff0c;我是Huber。 在美国当地时间 3 月 31 日&#xff0c;马斯克履行当初的诺言&#xff0c;他宣布了 Twitter 算法的…

《编程思维与实践》1048.解密字符串

《编程思维与实践》1048.解密字符串 题目 思路 主要到密码是升序的,所以先将每个数字对应的个数求出,之后升序排列输出即可得到结果. 求每个数字(0-9)对应的个数可以考虑每个英文单词中特有的字符(出现单次), zero,one,two,three,four,five,six,seven,eight,nine; 下面提供其中…

系统性能压力测试

系统性能压力测试 一、压力测试 压力测试是给软件不断加压&#xff0c;强制其在极限的情况下运行&#xff0c;观察它可以运行到何种程度&#xff0c;从而发现性能缺陷&#xff0c;是通过搭建与实际环境相似的测试环境&#xff0c;通过测试程序在同一时间内或某一段时间内&…

深度学习—卷积神经网络

卷积神经网络 传统意义上的多层神经网络只有输入层、隐藏层和输出层。其中隐藏层的层数根据需要而定&#xff0c;没有明确的理论推导来说明到底多少层合适。 卷积神经网络CNN&#xff0c;在原来多层神经网络的基础上&#xff0c;加入了更加有效的特征学习部分&#xff0c;具…

人生四维度

人生四维度 不是有钱了就成功&#xff0c;你知道&#xff1b;人生的成功不止一种&#xff0c;你也知道。但成功还有哪种&#xff1f;你知道吗&#xff1f; 如果把人生的体验展开&#xff0c;我们可以得到四个维度&#xff0c;高度、深度、宽度和温度。 财富、权力、影响力 构…

2023年3月股份行GX评测盘点:招商银行稳居榜首,各项指标均居前列

易观&#xff1a;2023 年3月GX评测数据显示&#xff0c;招商银行、平安口袋银行、中信银行位居行业Top 10&#xff0c;浦发银行、兴业银行、光大银行紧跟其后。 股份行APP 用户体验&#xff1a;招商银行以绝对优势稳居第一 2023年3月股份行GX评测结果数据显示&#xff0c;在操作…

VScode代码编辑器官网下载慢问题解决方法-亲测有效

VScode官网下载慢的问题如何解决&#xff1f; 问题描述&#xff1a; VisualStudioCode&#xff08;简称VSCode&#xff09;是Microsoft开发的一款功能强大的代码编辑器&#xff0c;它支持Windows&#xff0c;Linux和macOS等操作系统以及开源代码&#xff0c;因此被很多开发人…

maven从入门到精通 第四章 Maven中依赖的传递、排除、继承、聚合

这里写自定义目录标题 一 maven中依赖的传递1 依赖的传递性2 使用complie范围依赖spring-core3 测试依赖是否被传递4 依赖传递性的意义 二 maven中依赖的排除1 依赖排除概述2 具体操作依赖排除 三 maven中依赖的继承四 maven中依赖的聚合 一 maven中依赖的传递 1 依赖的传递性…