01数组算法/代码随想录

1 篇文章 0 订阅
1 篇文章 0 订阅

一、数组

好久没写算法题,之前喜欢按着习惯选择刷题,很早以前就听说代码随想录,今天跟着代码随想录再过一遍算法

1.1二分查找
  1. 常见疑问

    • middle一定是在[left, right]这个范围内
    • 标准代码不会越界,因为在else if中出现越界后,下一次循环就不会通过
  2. 左闭右闭区间

    • 代码示例

      public class BinarySearch<T> {
          public int BinarySearch(int[] arr, int target) {
              int left = 0, right = arr.length - 1, middle = 0;
              while (left <= right) {
                  middle = (left + right) / 2;
                  if (arr[middle] == target) {
                      return middle;
                  }
                  else if(arr[middle] > target) {
                      right = middle - 1;
                  }
                  else if(arr[middle] < target) {
                      left = middle + 1;
                  }
              }
              return -1;
          }
      
          public static void main(String[] args) {
              BinarySearch<Integer> binarySearch = new BinarySearch<>();
              int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
              System.out.println(binarySearch.BinarySearch(arr, 5));
          }
      }
      
    1. 为什么是left <= right,举一个极端例子[2, 2],这个时候如果是没有=,则无法返回值

    2. 为什么是middle - 1 middle + 1,因为在arr[middle] > targetarr[middle] < target的时候,已经判断过arr

      [middle]已不在范围内了,直接排除即可

  3. 左闭右开区间

    • 代码示例

      public class BinarySearch<T> {
          public int BinarySearch(int[] arr, int target) {
              int left = 0, right = arr.length - 1, middle = 0;
              while (left < right) {
                  middle = (left + right) / 2;
                  if (arr[middle] == target) {
                      return middle;
                  }
                  else if(arr[middle] > target) {
                      right = middle;
                  }
                  else if(arr[middle] < target) {
                      left = middle + 1;
                  }
              }
              return -1;
          }
      
          public static void main(String[] args) {
              BinarySearch<Integer> binarySearch = new BinarySearch<>();
              int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
              System.out.println(binarySearch.BinarySearch(arr, 5));
          }
      }
      
    1. 为什么是left < right,因为在极端情况下,比如不断缩减或一开始数组就是这样[2, 2),这明显错误,所以不存在相等
    2. 为什么是middle,因为是开区间,开区间本身就不包括在内
    3. 左开右闭同理,不赘诉
1.2双指针
1.2.1 快慢指针:拥有快慢指针,快指针先行,慢指针跟随条件移动
  1. 常用来数组元素的删除

    • 代码示例

      public int removeElement(int[] nums, int val) {
          int fast = 0, slow = 0;
          for (; fast < nums.length; ++fast) {
              if (nums[fast] != val) {
                  nums[slow] = nums[fast];
                  slow++;
              }
          }
          return slow;
      }
      
    • 实战

      1. 力扣27

        在这里插入图片描述

        暴力

        class Solution {
            public int removeElement(int[] nums, int val) {
                int n = nums.length;
                for(int i = 0; i < n; ++i) {
                    if (nums[i] == val) {
                        for (int j = i; j < n - 1; ++j) {
                            nums[j] = nums[j + 1];
                        }
                        n -= 1;
                        i -= 1;
                    }
                }
                return n;
            }
        }
        

        快慢指针

        class Solution {
            public int removeElement(int[] nums, int val) {
                int n = nums.length;
                int fast = 0; int slow = 0;
                while (fast < n) {
                    if (nums[fast] != val) {
                        nums[slow] = nums[fast];
                        slow++;
                    }
                    fast++;
                }
                return slow;
            }
        }
        
      • 在只有一个要移除的元素时和暴力的区别不大,只有在多个需要去除的元素才能体现出优势,但这里算是初步体现了快慢双指针的思想,让一个快指针去遍历每一个元素并判断每一个元素是否等于要删除的元素,慢指针代表索引号,这样避免了找到一次元素就要遍历一次的尴尬处境。这里体现了双指针思想,拥有两个自由的判断点,通过两种指针的交替配合从而避免暴力双层循环(暴力往往只有一个自由的判断点,外层循环的遍历),下面几题变种也体现了这种思想。
1.2.2 左右指针:两个指针相互操作,知道相撞
  1. 常用来比较大小并排序,实现O(n)时间复杂度

    • 代码示例

      public int[] sortedSquares(int[] nums) {
          int left = 0;
          int n = nums.length;
          int right = n - 1;
          int[] res = new int[n];
          while (right >= left) {
              if (nums[left] * nums[left] > nums[right] * nums[right]) {
                  res[n - 1] = nums[left] * nums[left++];
              } else {
                  res[n - 1] = nums[right] * nums[right--];
              }
              n -= 1;
          }
          return res;
      }
      
    • 这里为什么是right >= left和二分查找类似,因为都是左闭右闭的场景

    • 实战

      1. 力扣977

        在这里插入图片描述

        public int[] sortedSquares(int[] nums) {
            int left = 0;
            int n = nums.length;
            int right = n - 1;
            int[] res = new int[n];
            while (right >= left) {
                if (nums[left] * nums[left] > nums[right] * nums[right]) {
                    res[n - 1] = nums[left] * nums[left++];
                } else {
                    res[n - 1] = nums[right] * nums[right--];
                }
                n -= 1;
            }
            return res;
        }
        
      • 左指针和右指针都往中间靠,如果右指针比左指针大,则右指针–,如果左指针比右指针大,则左指针++,知道右指针小于左指针
1.2.3 滑动窗口
  1. 用来计算哪一段区间最符合要求

    • 实战

      1. 力扣209

        在这里插入图片描述

        暴力(失败,超出时间限制)

        class Solution {
          public int minSubArrayLen(int target, int[] nums) {
            int res = Integer.MAX_VALUE;
            int n = nums.length - 1;
            for (int i = 0; i <= n; ++i) {
                int add = 0;
                for (int j = i; j <= n; ++j) {
                    add += nums[j];
                    if (add >= target) {
                        res = Math.min(j - i + 1, res);
                        break;
                    }
                }
            }
            return res == Integer.MAX_VALUE ? 0 : res;
          }
        }
        

        滑动窗口

        public int minSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0;
        int n = nums.length;
        int result = Integer.MAX_VALUE;
        int subLength = 0;
        int sum = 0;
        for (; right < n; ++right) {
            sum += nums[right];
            while (sum >= target) {
                subLength = right - left + 1;
                result = Math.min(subLength, result);
                sum -= nums[left++];
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
        }
        
    • 若使用头指针,则和暴力没什么区别,所以选择尾部。滑动窗口一层循环则可直接计算,尾指针先移动,满足条件时,尾指针不动,头指针+1,再判断,如果通过,则更新result。否则继续移动尾指针,直到尾指针到数组尾部结束。

1.3模拟
  1. 对于难以应用算法的问题,可以通过模拟问题中的思路进行代码编写

    • 实战

    • 力扣54

      在这里插入图片描述

      class Solution {
          public List<Integer> spiralOrder(int[][] matrix) {
              List<Integer> res = new ArrayList<Integer>();
              int u = 0; int d = matrix.length - 1; int l = 0;
              int r = matrix[0].length - 1;
              while(true) {
                  for (int i = l; i <= r; ++i) {
                      res.add(matrix[u][i]);
                  }
                  if (++u > d) break;
                  for (int i = u; i <= d; ++i) {
                      res.add(matrix[i][r]);
                  }
                  if (--r < l) break;
                  for (int i = r; i >= l; --i) {
                      res.add(matrix[d][i]);
                  }
                  if (--d < u) break;
                  for (int i = d; i >= u; --i) {
                      res.add(matrix[i][l]);
                  }
                  if (++l > r) break;
              }
              return res;
          }
      }
      
    • 这是很常见的模拟之一,与力扣59互补,拿到这种题有常见的思路

      1. 大多数这种题目,都是给予条件,根据条件进行不断的计算。所以我们需要确定 1.循环中的计算 2.循环条件 3.终止条件

        • 上述写法是比较快的,有些地方是较优解的

          • 我比较喜欢使用while循环,因为while循环往往可以表示确定次数的循环和不确定次数的循环,for循环是确定次数的。但for循环也有一些优势,简洁明了,很容易判断循环条件

          • 拿与力扣59举例,那题是一个正方形,所以只会出现一种特殊情况就是层数为奇数的时候,需要手动添加垂直水平中间数组中的值,而这里行列是不一样的,所以终止的情况有多种,上下左右都有可能,所以直接在while循环中添加判断是比较复杂的

          • 这里,我再说个结论,中间的循环写法很大程度依靠终止条件的写法。力扣59强调每一个方向使用相同开闭区间的数组。这里是每一个变都是闭区间,是因为59题是依靠着一个实时的变量来控制数字在数组中的走向,这里判断条件不好写绕圈次数,因为上面说过终止条件判断较多,所以不太好靠一个实时变量来控制走向。由于终止条件很多,所以这里引用上下左右边界,就很好的解决了循环内写法的问题

          • 这种模拟的方法效率更高,大家在写模拟的时候,可以先将思路列举好,起始条件、终止条件、遍历写法,然后再一步一步优化

  • 总结

    1. 双指针技术的魅力就在于它通过一个主循环来控制遍历,同时在循环内进行值的计算和条件判断,从而避免了不必要的嵌套循环。这种方式不仅提高了效率,还使得代码更加简洁和易于理解。所以写双指针的时候需要在一层循环就进行计算,

      • 以下的写法就是为了写双指针而写,实际上就是暴力,没有做到第一层循环就计算,力扣209

        public int minSubArrayLen(int target, int[] nums) {
            int start = 0, end = 0;
            int n = nums.length;
            int res = Integer.MAX_VALUE;
        
            while (end < n) {
                int add = 0;
                int i = start;
        
                // 计算窗口内的和
                for (; i <= end; i++) {
                    add += nums[i];
                    if (add >= target) {
                        res = Math.min(end - start + 1, res);
                        break;  // 找到满足条件的和后,退出循环
                    }
                }
        
                // 如果找到满足条件的和,移动 start
                if (add >= target) {
                    start++;
                } else {
                    end++;  // 否则,移动 end
                }
            }
        
            return res == Integer.MAX_VALUE ? 0 : res;  // 如果没有找到,返回 0
        }
        
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值