二刷第一天(二分查找,移除元素)+数组新题三道:LeetCode26. 删除有序数组中的重复项、LeetCode283. 移动零、LeetCode844. 比较含退格的字符串

一.二分查找:

                ①二分查找应用于有序数组寻找位置问题。

                ②根据需要寻找的值和区间中值的关系更新左右边界值。

                ③在中值右边,更新左边界;在中值左边,更新右边界。

                ④要注意:确定好是左闭右闭还是左闭右开

代码:

//二分查找要根据区间来判断的,通过目标值和区间中间值的大小关系,来变换区间边界
class Solution {
    public int search(int[] nums, int target) {
        if(target < nums[0] || target > nums[nums.length-1]){
            return -1;
        }
       return helper(nums, target);
    }
    private int helper(int[] nums, int target){
        int left = 0;
        int right = nums.length-1;
       while(left <= right){
           int mid = (left+right)/2;
           if(target > nums[mid]){
              left = mid+1;
           }else if(target < nums[mid]){
               right = mid-1;
           }else{
               return mid;
           }
       }
       return -1;
    }
}

二.移除元素:

                ①暴力的解法:也是数组的移除的基本思想---遇到要移除的元素了,将其后面所有的元素都向前移一位,然后总长度-1.

                ②Note:要注意的就是,因为此时移动的时候,原来i+1的位置的值现在移动到了i位置,所以要判断原i+1位置是否是要删除的元素,我们还要i--

代码:

//暴力解法,遍历数组,遇到相等的,就把后面的都向前移一位。
//要注意的就是移动完之后,i+1位置的值,变成了i位置的值,而原来i+1位置的值还未与val比较即现在i位置的值未与val比较所以要i--
//还有一点要注意,因为我们的size删完一个元素要减减,所以在写循环的时候的右边界是size而不一直是length
class Solution {
    public int removeElement(int[] nums, int val) {
    if(nums.length == 0){
        return 0;
    }
    int size = nums.length;
    for(int i = 0; i < size; i++){
        if(nums[i] == val){
            for(int j = i+1; j < size; j++){
                nums[j-1] = nums[j];
            }
            i--;
            size--;
        }
    }
    return size;
    }
}

                ③双指针解法:快慢指针初始均指在下标为0的位置,快指针负责遍历数组慢指针负责收集最后的结果,因为我们不用考虑新数组其余位置的值,即返回新数组的长度和对应的值即可。

                ④那么怎么模拟移除元素呢?就是当遇到要移除的元素,我们直接不处理,即直接跳过,就想当于移除了,而遇到的元素不是要删除的元素的时候,直接收集,并且慢指针++。

                ⑤慢指针的值表示的就是下一个元素的位置,当然到最后的时候,它表示的就是新数组的长度

代码:

//双指针做法,即想要删除元素,那么碰到要删的元素的时候就直接跳过,用慢指针记录新的数组,用快指针遍历数组,最后因为要求的是数组的长度,直接返回慢指针即可。
class Solution {
    public int removeElement(int[] nums, int val) {
    if(nums.length == 0){
        return 0;
    }
      int slowIndex = 0;
      for(int fastIndex = 0; fastIndex < nums.length;fastIndex++){
        if(nums[fastIndex] != val){
            nums[slowIndex] = nums[fastIndex];
            slowIndex++;
        }
    }
      return slowIndex;
    }
}

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

题目描述:

                给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

                由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

                将最终结果插入 nums 的前 k 个位置后返回 k 。

                不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件

解法:

                ①首先我们看到删除数组,可以试试双指针写法。

                ②确定快慢指针指向哪?因为本题求没有重复的数组,那么第一个元素肯定不重复,所以快慢指针都指向下标为1的位置

                ③重复的话要删掉,所以依旧快指针遍历,慢指针收结果,即当前的元素如果和前面的元素重复了的话,就跳过,否则的话就收集,收集的同时要注意慢指针++。

                ④慢指针仍旧表示的是最后的新数组的长度。

代码:

//慢指针的数组中最开始就有一个元素,因为一个元素不可能重复。
//要用快指针去比较当前的和前一个是否相等,所以要从1到nums.length-1
//然后当不等的时候取当前元素
//最后返回满指针就是数组的长度
class Solution {
    public int removeDuplicates(int[] nums) {
        int slowIndex = 1;//刚开始有一个元素,所以下一个收集位置是1.
        // nums[slowIndex] = nums[0];
        // slowIndex++;
        for(int fastIndex = 1; fastIndex < nums.length; fastIndex++){
            if(nums[fastIndex] != nums[fastIndex-1]){
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
    return slowIndex;
    }
}

四. 移动零

题目描述:

                给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

                请注意 ,必须在不复制数组的情况下原地对数组进行操作。

解法:

                ①此题其实和移除元素很类似。即遇到0的跳过,那么要解决的问题就是收集非是0的元素后,剩余的0应该从哪开始填充呢?

                ②在移除元素的那题中,slowIndex既表示下一个要收集的位置也表示新数组的长度。

所以就从slowIndex到元素末尾都赋0即可。

代码:

//碰到不是0的收集,到最后,slowIndex的位置就是第一个0的位置,到末尾都置0即可
class Solution {
    public void moveZeroes(int[] nums) {
        int slowIndex = 0;
        for(int fastIndex = 0; fastIndex < nums.length; fastIndex++){
            if(nums[fastIndex] != 0){
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        for(int j = slowIndex; j < nums.length; j++){
            nums[j] = 0;
        }

    }
}

五. 比较含退格的字符串

题目描述:

                给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

                注意:如果对空文本输入退格字符,文本继续为空。

解法:

                首先要理解题意,即#会移除掉其前面一个位置的元素,同时#位置处的元素也被移除掉了。

                此题其实也相当于删掉符合题目要求的元素得到新数组,故考虑双指针做法:

                ①双指针的指向分别为s和t的末尾,因为#会移除其前面的元素,所以我们要从后向前遍历,所以指向s和t的末尾。

                ②我们考虑什么时候是表示两个字符串相等,即他俩同时遍历完且遍历的过程中不出现false的情况,故大循环条件 --- while(i >= 0 || j >= 0)即只有同时遍历完之后才跳出,然后返回true。

                ③对于每一个小的while循环,遇到# 计数器++,i--;没遇到#,计数器>0,计数器--,i--;没遇到#,且计数器 ==0 ,跳出小循环比较。

                ④比较:i>=0 && j>=0,对应的值不等,false。

                              否则的话,如果 i>=0 || j >=0 ,false。--- 即一个有当前位置的元素,一个已经遍历完了且没有当前元素,故一定是一个字符串比另外一个字符串至少多一个字符。

                              剩下的情况就是都大于0,值相等,此时是匹配上了,i--,j--。

                              还有就是都小于0,那么此时再--更小于0,即跳出大循环,结束比较。

                ⑤经历过比较后,没有出现错误,就返回true。

代码:

//此题不论怎么做都应该想到,#是删前面的值,所以我们要从后向前遍历。
//用双指针来做,一个指向S的末尾,一个指向T的末尾
//首先要明确,当两个指针同时为负数并且中途比较没有错误,最后返回true;故大的while的条件就是i>=0||j>=0
//对于每一个指针,遇到了#,计数器++,指针前移;没遇到#,计数器>0,计数器--,指针前移;没遇到#,计数器=0,直接跳出循环进行比较。
//比较:如果ij同时>=0,且数值不等,就false
//否则的话,只要有1方大于0,就是false。
//剩下的情况就是都>=0,且相等;和同时小于0
//都大于0且相等,说明匹配上了,ij同时前移---i--,j--;同时小于0,说明比较完毕,下面又--了更小于0了,就直接跳出循环。
//循环外表示的就是,比较完两个字符串不出现false的情况,所以return true。
//3 while 1 if
class Solution {
    public boolean backspaceCompare(String S, String T) {
        char[] ch1 = S.toCharArray();
        char[] ch2 = T.toCharArray();
       int i = ch1.length - 1;
       int j = ch2.length - 1;
       int count1 = 0;
       int count2 = 0;
       while(i >= 0 || j >=0){
           while(i >= 0){
               if(ch1[i] == '#'){
                   i--;
                   count1++;
               }else if(count1 > 0){
                   count1--;
                   i--;
               }else{
                   break;
               }
           }
           while(j >= 0){
               if(ch2[j] == '#'){
                   j--;
                   count2++;
               }else if(count2 > 0){
                   j--;
                   count2--;
               }else{
                   break;
               }
           }
           if(i >= 0 && j >= 0){
               if(ch1[i] != ch2[j]){
                   return false;
               }
           }else{
               if(i >=0 || j >= 0){
                   return false;
               }
           }
       i--;
       j--;
    }
    return true;
    }
}

                ⑥用StringBuilder来做:即写一个函数用于获得满足条件后的新字符串:

                ⑦用while循环,遇到# 计数器++,i--;没遇到#,计数器>0,计数器--,i--;没遇到#,且计数器 ==0 ,说明匹配上了,就append该字符,并且i--。

                ⑧最后调用主函数进行比较得到结果即可。

代码:

//用StringBuilder来做:统计#出现的次数,如果当前元素是#,i--,count++;如果当前元素不是#,且count>0,i--,count++;否则的话;append元素,i--;最后调用主函数比较返回结果。
class Solution {
    public boolean backspaceCompare(String S, String T) {
        return convert(S).equals(convert(T));
    
    }
    private String convert(String s){
        StringBuilder sb = new StringBuilder();
        int i = s.length()-1;
        int count = 0;
        while(i >= 0){
            if(s.charAt(i) == '#'){
                count++;
                i--;
            }else if(count > 0){
                i--;
                count--;
            }else{
                sb.append(s.charAt(i));
                i--;
            }
        }
        return sb.toString();
    }
}

两种方法比较:

                ①双指针的时间复杂度是O(m+n),空间复杂度是O(1)

                ②用StringBuilder的时间复杂度是O(m+n),空间复杂度也是O(m+n)---因为要动态开辟两个空间用于存储新得到的字符串。但是StringBuider的方法代码更简单,也更容易理解些。

六.总结:

                ①复习了两道旧题,对于二分法和移除元素有了更深的理解---分别为对应区间问题和双指针问题。

                ②剩下的三道题都是双指针问题,最后形成的思路就是遇到移除数组中元素问题,可以考虑双指针的思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值