力扣LeetBook<数组与字符串>学习笔记

LeetBook地址

数组和字符串

一、数组简介

1、集合、列表和数组

数组是数据结构中的基本模块之一。因为字符串是由字符数组形成的,所以二者是相似的。大多数面试问题都属于这个范畴。

1.1、集合

  • 集合一般被定义为:由一个或多个确定的元素所构成的整体。
  • 集合里的元素类型不一定相同
  • 集合里的元素没有顺序。

1.2、列表

  • 列表(又称线性列表)的定义为:是一种数据项构成的有限序列,即按照一定的线性顺序,排列而成的数据项的集合
  • 元素类型可以 不同
  • 具有顺序,且长度是可变的
  • 最常见的表现形式有数组和链表,而栈和队列则是两种特殊类型的列表。
  • 列表中的元素在内存中可能相邻也可能不相邻,比如链表的元素在内存中不一定是连续的

1.3、数组

  • 数组是列表的实现方式之一
  • 数组有索引,列表没索引
  • 其次,数组中的元素在内存中是连续存储的,每个元素占用相同大小的内存。

2、数组的操作

数组的4种操作(读查插删)

2.1、读取元素

通过索引,O(1)

2.2、查找元素

O(N),N

2.3、插入元素

O(N)

2.4、删除元素

O(N),N为数组的长度

3、例题

3.1、寻找数组的中心索引

724.寻找数组的中心索引

给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。
我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。

示例 1:
输入:
nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6 = 11) 相等。
同时, 3 也是第一个符合要求的中心索引。

示例 2:
输入:
nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心索引。


​ 说明:

​ nums 的长度范围为 [0, 10000]。
​ 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。
我的代码:用的双指针思想,一个指在最左端,一个指到中心索引后一位。

    public int pivotIndex(int[] nums) {
        if (nums == null || nums.length < 3)
            return -1;
        for (int i = 0; i < nums.length; i++){
            int lSum = 0;
            int rSum = 0;
            for (int j = 0; j < i; j++){
                lSum += nums[j];
            }
            for (int j = i + 1; j < nums.length; j++){
                rSum += nums[j];
            }
            if (lSum == rSum){
                return i;
            }
        }
        return -1;
    }

在这里插入图片描述
官方题解:
方法:前缀和
算法:

  • S 是数组的和,当索引 i 是中心索引时,位于 i 左边数组元素的和 leftsum 满足 S - nums[i] - leftsum。

  • 我们只需要判断当前索引 i 是否满足 leftsum==S-nums[i]-leftsum 并动态计算 leftsum 的值。

class Solution {
    public int pivotIndex(int[] nums) {
        int sum = 0, leftsum = 0;
        for (int x: nums) sum += x;
        for (int i = 0; i < nums.length; ++i) {
            if (leftsum == sum - leftsum - nums[i]) return i;
            leftsum += nums[i];
        }
        return -1;
    }
}

3.2、搜索插入位置

35. 搜索插入位置

这道题考察我们的比较大小以及对边界索引的理解。(看完评论才知道我用的叫暴力法)

class Solution {
    public int searchInsert(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            if (target <= nums[i]) {
                return i;
            }
        }
        return nums.length;
    }
}

在这里插入图片描述
官方题解:
我看到官方说是用二分法解答,这里我也实现一下吧。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length;
        while(left < right){
//            int mid=(left+right)/2;
            int mid = left + (right - left) / 2;
            if(nums[mid] >= target){
                right = mid;
            }
            else
                left = mid + 1;
        }
        return left;
    }
}
    public int searchInsert(int[] nums, int target) {
        int l = 0, h = nums.length - 1;
        while (l <= h) {
            int mid = l + (h - l) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                h = mid - 1;
            } else {
                l = mid + 1;
            } 
        }
        return l;
    }

有两种计算中值 m 的方式:

  • m = (l + h) / 2
  • m = l + (h - l) / 2

l + h 可能出现加法溢出,也就是说加法的结果大于整型能够表示的范围。但是 l 和 h 都为正数,因此 h - l
不会出现加法溢出问题。所以,最好使用第二种计算法方法。

3.3、合并区间

56. 合并区间
在这里插入图片描述

满足合并区间的条件:
ints[i][1]>=ints[i+1][0]
合并区间:

左端点为ints[i][0],右端点为max(ints[i][1],ints[i+1][1]

返回的结果用什么存储:

能直接操作目标数组吗?不能,因为结果数组长度可能会变。
能新建一个辅助二维数组来保存结果吗?不能,理由同上。
既然不知道结果数组长度,那就得借助高级一点的数据结构了,首先考虑ArrayList
ArrayList.add方法能让我们很方便地加入区间。
ArrayList.get方法可以获取指定索引的内容
最后再用toArray方法变回二维数组

难点:

循环次数不易确定,而且涉及i+1的操作,容易越界。这种情况用while循环会更容易。
学会用Arrays.sort进行升序排序。
熟练掌握同向双指针的使用。

代码:

public int[][] merge(int[][] intervals) {
    List<int[]> res = new ArrayList<>();
    if (intervals.length == 0 || intervals == null) return res.toArray(new int[0][]);
    Arrays.sort(intervals, (intervals1,intervals2) -> (intervals1[0] - intervals2[0]));//如果不会lambda表达式也可以用匿名内部类实现
    int i = 0;
    while (i < intervals.length) {
        int left = intervals[i][0];
        int right = intervals[i][1];
        while (i < intervals.length - 1 && intervals[i + 1][0] <= right) {
            i++;
            right = Math.max(right, intervals[i][1]);
        }
        res.add(new int[]{left, right});
        i++;
    }
    return res.toArray(new int[0][]);
}

在这里插入图片描述
ArrayList初始化、常用方法总结

二、二维数组简介

  • 二维元素的每个元素变成了一维数组。
  • 本质上仍然是一维数组,我们可以把它看作一个矩阵,并处理矩阵相关问题。
  • 同样在内存中申请一段连续的空间

2.png

注意,实际数组中的元素由于类型的不同会占用不同的字节数,因此每个方格地址之间的差值可能不为 1。

实际题目中,往往使用二维数组处理矩阵类相关问题,包括矩阵旋转、对角线遍历,以及对子矩阵的操作等。

1、旋转矩阵

面试题 01.07. 旋转矩阵

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。

不占用额外内存空间能否做到?

在这里插入图片描述

找出等式是做这种题的关键,其中i为行数,j为列数:
m a t r i x 1 [ j ] [ m l e n g t h − 1 − i ] = m a t r i x [ i ] [ j ] matrix1[j][mlength - 1 - i] = matrix[i][j] matrix1[j][mlength1i]=matrix[i][j]
法1:使用辅助数组

    public void rotate(int[][] matrix) {
        int mlength = matrix.length;
        int[][] matrix1 = new int[mlength][mlength];//记住后面一个也要写上,不然会报空指针错误
        for (int i = 0; i < mlength; i++){
            for (int j = 0; j < mlength; j++){
                matrix1[j][mlength - 1 - i] = matrix[i][j];
            }
        }
        for (int i = 0; i < mlength; i++){
            for (int j = 0; j < mlength; j++){
                matrix[i][j] = matrix1[i][j];
            }
        }
    }

法2:翻转法

        public void rotate(int[][] matrix) {
            int mlength = matrix.length;
            //水平翻转
            for (int i = 0; i < mlength/2; i++){
                for (int j = 0; j < mlength; j++){
                    int temp = matrix[i][j];
                    matrix[i][j] = matrix[mlength - 1 - i][j];
                    matrix[mlength - 1 - i][j] = temp;
                }
            }
            //主对角线"\"翻转
            for (int i = 1; i < mlength; i++){
                for (int j = 0; j < i; j++){
                    int temp = matrix[i][j];
                    matrix[i][j] = matrix[j][i];
                    matrix[j][i] = temp;
                }
            }
        }

在这里插入图片描述

2、零矩阵

面试题 01.08. 零矩阵

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

重点就是if (matrix[i][j] == 0)后怎么处理,我一开始是想尝试在if里面清零,但这种方法显然不可取的。因为清零后会影响后续的循环判断,导致更多的数据清0。那我们在外面做清零功能吧。

清理行和清理列两个功能分开来看就很简单,所以这里设置了两个boolean数组,通过置true来标记需要清零的行数和列数。

当然你也可以用Int[]数组,置1。

    public void setZeroes(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        boolean[] row = new boolean[m];
        boolean[] column = new boolean[n];
        //寻找需要清零的行数和列数
        for (int i = 0; i < m; i++){
            for (int j = 0; j < n; j++){
                if (matrix[i][j] == 0){
                    row[i] = true;
                    column[j] = true;
                }
            }
        }
        //清零行
        for (int i = 0; i < m; i++){
            if (row[i]){
                matrix[i] = new int[n];//这种方法能快速清零,不需要两个for循环
            }
        }
        //清零列
        for (int i = 0; i < n; i++){
            if (column[i]){
                for (int j = 0; j < m; j++){
                    matrix[j][i] = 0;
                }
            }
        }
    }

在这里插入图片描述

3、对角线遍历

498. 对角线遍历

给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。

在这里插入图片描述

img

第一先观察规律,发现

  • 每轮遍历后,索引值的和+1 ——可以通过行列索引值自增或自减来控制遍历方向不变,或者跳到下一遍历
  • 索引值和的奇偶性决定了遍历的方向——if((row+col)%2==0)来控制方向
  • 既然已经控制好方向了,我们就要分向上遍历和向下遍历两种情况来思考了。

下图源自@力扣 (LeetCode),不得不说官方画的图就是好,简洁又清晰,不得不说画图很重要啊,如果你像上面例题给的图那样画,画再多可能也看不出规律,理不清思路。

在这里插入图片描述
看到这些运动有规律的箭头,你能想到一个很重要的方法吗?就是双指针。一个指向行索引,一个指向列索引,这样我们操作起来就很方便了。

  1. 向上遍历
  • 向上遍历的起点总是在第一列
  • 向上遍历实质就是向右上移动,每次要做的操作就是行索引++,列索引–
  • 终点总是在第一行,紧接着列索引++来右移跳到向下遍历的起点
  • 当向上到达最后一列,即右上角(如图中的从17到5),这时候如果不加限制条件,列坐标就继续向右移动,越界了。所以要加上行坐标c++的代码,就能从10继续开始了。

易错点是if和else if的顺序不能弄乱,否则遍历到了边角会出错。

  1. 向下遍历

    同理。

    public int[] findDiagonalOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) {
            return new int[]{};
        }
        int r = 0, c = 0;//行索引值和列索引值
        int row = matrix.length, col = matrix[0].length;
        int[] res = new int[row * col];
        for (int i = 0; i < res.length; i++) {
            res[i] = matrix[r][c];
            //偶数向上遍历
            if ((r + c) % 2 == 0) {
                if (c == col - 1) {//列坐标到达最后一列
                    //往下移动一格准备向下遍历
                    r++;
                } else if (r == 0) {//行坐标到第一行
                    //往右移动一格准备向下遍历
                    c++;
                } else {
                    // 往右上移动
                    r--;
                    c++;
                }
            } else {//奇数,向下遍历
                if (r == row - 1) {//行坐标到达最后一行
                    // 往右移动一格准备向上遍历
                    c++;
                } else if (c == 0) {
                    // 往下移动一格准备向上遍历
                    r++;
                } else {
                    // 往左下移动
                    r++;
                    c--;
                }
            }
        }
        return res;
}

我一开始是在typora写的,刚把它导入进csdn出现了问题,发现图片都没了,提示外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传。但gitee图床也配置好了,上传也成功了,不知道为什么就是显示不了。浪费了好多时间,还是先在csdn上码字吧。

三、字符串简介

  • 字符串基本操作对象通常是字符串整体或者其子串
  • 字符串操作比其它数据类型更复杂(如比较、连接操作)

如果你确实希望你的字符串是可变的,则可以使用 toCharArray 将其转换为字符数组。
如果你经常必须连接字符串,最好使用一些其他的数据结构,如 StringBuilder

1、最长公共前缀

14. 最长公共前缀

在这里插入图片描述

第一次做的思路是先排序,把长度最小的字符串放到第0位,然后依次抽取这个字符串的每个字符,与剩下的字符串对应位置的字符做比较,和剩下的字符串都相同则可以拼接进str。

    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        String str = "";
        Arrays.sort(strs, (o1, o2) -> (o1.length() - o2.length()));
        if (strs.length == 1){
            return strs[0];
        }
        for (int i = 0; i < strs[0].length(); i++){
            char c = strs[0].charAt(i);
            for (int j = 1; j < strs.length; j++){
                if (c != strs[j].charAt(i)){
                    return str;
                }
                if (j == strs.length-1){
                    str += c;
                }
            }
        }
        return str;
    }

在这里插入图片描述
再也没有比这个更糟的代码了,可能是因为我对字符串不够了解。
感觉字符串操作比数组繁杂,也没数组那么直观的等式,我先好好复习了一下String类的知识。


String类总结
然后发现了很多有用的方法,像startWith,substring等方法,看看能不能在这道题用上。
总体思路还是和刚才一样,但这次是妙用了substring方法,注意substring(0,0)返回的是空字符串,这就很有用了。

    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        for (int i = 0; i < strs[0].length(); i++) {
            char c = strs[0].charAt(i);
            for (int j = 1; j < strs.length; j++) {
            //如果之前升序排序了,strs[0]一定是最短长度,就不用i==strs[j].length()了
                if (i == strs[j].length() || strs[j].charAt(i) != c) {
                    return strs[0].substring(0, i);
                }
            }
        }
        //当数组只有一个元素字符串时
        //abc,abcd,abcde
        return strs[0];
    }

总结一下这种题型需要注意的三个点

  • 非空判断,通常return “”;
  • 数组里只有一个元素的判断
  • 题目要求情况的判断

2、最长回文子串

5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

先写一个判断是否是回文子串的方法valid,方便调用。发现回文子串具有对称性,用双指针
要判断回文子串,其实就涉及到了区间问题,你会发现这样的代码是很有代表性的。

  for (int i = 0; i < s.length() - 1; i++){
            for (int j = i + 1; j < s.length(); j++){

正常思路写下来,我卡在

if ( valid(s,i,j)) res = s.substring(i, j+1);

这里,我一开始这样做得到的只是最后一个回文子串。我思考怎么才能获取的是最大长度的回文子串呢?所以,必须先往if条件里加东西,长度不就是j-i+1吗?那如果j-i+1是最大的,这个就是我们要的。
回想选择排序时就用到的思想,不难想到我们把第一个回文子串的长度当成最大长度,然后如果后面出现有比它大的那就换成后面那个。
下面是代码,注意我上一题总结的三点注意,在这个代码你找到了吗?只不过这里的注意2不是只有一个字符串的情况了,而是只有一个字符的情况。

    public String longestPalindrome(String s) {
    	//注意1
        if (s == null || s.length() == 0){
            return "";
        }
        int maxLen = 0;
        String res = s.substring(0,1);//注意2
        for (int i = 0; i < s.length() - 1; i++){//注意3
            for (int j = i + 1; j < s.length(); j++){
                if (j - i + 1 > maxLen && valid(s,i,j)){
                    maxLen = j-i+1;
                    res = s.substring(i, j+1);
                }
            }
        }
        return res;
    }

      private boolean valid(String s, int left, int right) {
        while (left < right){
            if (s.charAt(left) != s.charAt(right)){
                return false;
            }else {
                left++;
                right--;
            }
        }
        return true;
    }

2021-03-15更新:

    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        int n = s.length();
        int maxLen = 0;
        String ans = s.substring(0, 1);
        boolean[][] dp = new boolean[n][n];
        char[] chars = s.toCharArray();
        for (int j = 1; j < n; j++) {
            for (int i = 0; i < j; i++) {
                if (chars[i] == chars[j]) {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    ans = s.substring(i, j + 1);
                }
            }
        }
        return ans;
    }

O(n²),O(n²)

3、翻转字符串里的单词

151. 翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。

说明:

无空格字符构成一个 单词 。 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

终于遇到一道能五分钟内写完的题目了,看来String类的总结没白写。这道题思路很简单,前提是对String方法熟悉。

    public String reverseWords(String s) {
    	if (s == null && s.length() == 0) {
            return "";
        }
        String[] split = s.trim().split(" +");
        String str = "";
        for (int i = split.length - 1; i >= 0; i--){
            str += split[i]+" ";
        }
        return str.trim();
    }

看了一下别人的,发现还能更简单。。。

    public String reverseWords(String s) {
        String[] words = s.trim().split(" +");
        Collections.reverse(Arrays.asList(words));
        return String.join(" ", words);
    }

原来Collections除了sort外还有这么好用的方法:

reverse(List list)
反转指定List集合中元素的顺序

看来我对Java类库方法不熟悉啊,得系统学学了。

4、(选修)字符串匹配算法:KMP

早已听闻它的大名,这座大山还是以后再攀登吧。

5、实现strStr()

28. 实现 strStr()

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置
(从0开始)。如果不存在,则返回 -1。

思路没啥好说的,跟最长回文字符串差不多的思想。

    public int strStr(String haystack, String needle) {
        for (int i = 0; i < haystack.length(); i++) {
            for (int j = i + 1; j <= haystack.length(); j++) {
                if (haystack.substring(i, j).equals(needle)) {
                    return i;
                }
            }
        }
        return needle.length() == 0 ? 0 : -1;
    }

在这里插入图片描述
我还是第一次遇到这种情况,这应该是说我其他的都通过了,就差这个?那说明一定有其他简单的办法,出题者想让我们用那些方法。再看了看题目,这不就是求索引吗。

    public int strStr(String haystack, String needle) {
        return haystack.indexOf(needle);
    }

啊这……

在这里插入图片描述
这波啊,这波是把简单问题复杂化了。可这样就没意思了,我又想了十分钟,还是找不到其他的办法来搞。看官方题解:


方法一:子串逐一比较 - 线性时间复杂度
最直接的方法 - 沿着字符换逐步移动滑动窗口,将窗口内的子串与 needle 字符串比较。
在这里插入图片描述

class Solution {
  public int strStr(String haystack, String needle) {
    int L = needle.length(), n = haystack.length();

    for (int start = 0; start < n - L + 1; ++start) {
      if (haystack.substring(start, start + L).equals(needle)) {
        return start;
      }
    }
    return -1;
  }
}

方法二:双指针 - 线性时间复杂度
上一个方法的缺陷是会将 haystack 所有长度为 L 的子串都与 needle 字符串比较,实际上是不需要这么做的。

首先,只有子串的第一个字符跟 needle 字符串第一个字符相同的时候才需要比较。

  public int strStr(String haystack, String needle) {
    int L = needle.length(), n = haystack.length();
    if (L == 0) 
        return 0;
    for (int i = 0; i < n - L + 1; ++i) {
        for (int j = 0; j < L; ++j) {
            if (haystack.charAt(i + j) != needle.charAt(j))
                break;
            if (j == L - 1)
                return i;
        }
    }
    return -1;
  }

6、长键按入

925. 长按键入

你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。
你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。

在这里插入图片描述

难点是什么呢?难点是我们不知道两个字符串哪个长哪个短,还有循环的结束条件又是什么?把上面的理清了才能charAt不越界。像我们这种做的题手指都可以数过来的人,最好先写几种情况来模拟一下,毕竟笔试面试的时候你就不能面向测试编程了。

一、len1>len2:
1.leetc		letc ×
2.leetc		leetc√
二、len1<len2:
1.leetc		leeettc √
2.leetc		llettcc ×
3.leetc		leetcode ×
三、len1=len2:
1.leetc		leeta ×
2.leetc		leetc √

总结规律:

  1. 没遍历完name,typed没遍历完 ×
  2. 没遍历完name,typed遍历完了 ×
  3. 遍历完name了,但typed没遍历完 ×
  4. 遍历完name了,typed也遍历完了 √

所以我们看到只有两个都遍历完了才返回正确,而且len1 <= len2,所以外内循环里的条件会写了吧?

    public boolean isLongPressedName(String name, String typed) {
    	//非空判断
        if (name == null || typed == null) {
            return false;
        }
        //双指针
        int i = 0, j = 0;
        while (j < typed.length()) {//j和i这里不能换
            if (i < name.length() && name.charAt(i) == typed.charAt(j)) {//i < name.length()必须放前面。除非你想体验一下name.charAt(name.length())
                i++;
                j++;
            }else if (j > 0 && typed.charAt(j) == typed.charAt(j - 1)) {
                j++;
            }else {
                return false;
            }
        }
        return i == name.length();
    }

另外三点注意你找到了吗?不过需要一提的是,第二点注意往往依赖于第三点。因为第二点注意的产生很多时候是因为它进不去第三点注意的循环里。而且他们经常会结合在一起在最后进行return,这里巧妙地用了return i == name.length();来包含多种情况。

四、双指针技巧

1.双指针技巧——情景1

在这里插入图片描述

使用双指针技巧,其思想是分别将两个指针分别指向数组的开头及末尾,然后将其指向的元素进行交换,再将指针向中间移动一步,继续交换,直到这两个指针相遇。

我们来总结一下,使用双指针的典型场景之一是你想要

从两端向中间迭代数组。

这时你可以使用双指针技巧:

一个指针从头部开始,而另一个指针从尾部开始。

这种技巧经常在排序数组中使用。

1.1反转字符串

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

在这里插入图片描述

我一开始想试试一行代码解决:

s = new StringBuffer(String.valueOf(s)).reverse().toString().toCharArray();

但和题意要求不符。


while循环版本:

    public void reverseString(char[] s) {
        int i = 0, j = s.length - 1;
        while (i < j) {
            char temp = s[i];
            s[i] = s[j];
            s[j] = temp;
            i++;
            j--;
        }
    }

for循环版本:

    public void reverseString(char[] s) {
        for (int i = 0, j = s.length - 1; i < j; i++,j--) {
            char temp  = s[i];
            s[i] = s[j];
            s[j] = temp;
        }
    }

1.2数组拆分Ⅰ

561.数组拆分

给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。返回该 最大总和 。

在这里插入图片描述

    public int arrayPairSum(int[] nums) {
        Arrays.sort(nums);
        int sum = 0;
        for (int i = 0; i < nums.length - 1; i += 2) {
            sum += nums[i];
        }
        return sum;
    }
    public int arrayPairSum(int[] nums) {
        Arrays.sort(nums);
        int i = 0, j = nums.length - 2, sum = 0;
        while (i <= j) {
            if (i != j) sum += nums[i] + nums[j];
            else sum += nums[i];
            i += 2;
            j -= 2;
        }
        return sum;
    }

1.3两数之和Ⅱ

167. 两数之和 II - 输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
    在这里插入图片描述
    public int[] twoSum(int[] numbers, int target) {
        if (numbers == null) return null;
        int l = 0, h = numbers.length - 1;
        while (l < h) {
            int sum = numbers[l] + numbers[h];
            if (sum > target) {
                h--;
            }else if (sum < target) {
                l++;
            }else
                return new int[]{l+1,h+1};
        }
        return null;
    }

双指针技巧——情景2(快慢指针)

有时,我们可以使用两个不同步的指针来解决问题,即快慢指针。与情景一不同的是,两个指针的运动方向是相同的,而非相反。

我们就可以采用快慢指针的思想:初始化一个快指针 fast 和一个慢指针 slow,fast 每次移动一步,而 slow 只当 fast 指向的值不等于 val 时才移动一步。

在这里插入图片描述
小结:
这是你需要使用双指针技巧的另一种非常常见的情况:

同时有一个慢指针和一个快指针。

解决这类问题的关键是:

确定两个指针的移动策略。

与前一个场景类似,你有时可能需要在使用双指针技巧之前对数组进行排序,也可能需要运用贪心法则来决定你的运动策略。

2.1移除元素

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

在这里插入图片描述

照着上面的小结来理一下快慢指针做题步骤:

1. 是否需要对数组进行排序
2. 定义一个快指针和一个慢指针
3. 确定两个指针的移动策略,通常是快指针每个循环都移动,慢指针看快指针指向的目标和题目要求指定移动策略
 (可能需要运用**贪心法则**来决定运动策略)

这道题来说:

  1. 不需要排序
  2. slow慢指针,定义在循环条件前,因为要作为返回值。fast指针照例定义在循环条件内
  3. fast每次循环自增1,slow在fast指的值!=val才自增1,其实就用这个指针停留在要替换的值。

正常方法:

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

下面这种方法适合移除元素较少的输入,而且前提是顺序可变

    public int removeElement(int[] nums, int val) {
        int i = 0, len = nums.length;
        while (i < len) {
            if (nums[i] == val) {
                nums[i] = nums[len-1];
                len--;
            } else {
                i++;
            }
        }
        return len;
    }

2.2最大连续1的个数

485. 最大连续1的个数

给定一个二进制数组, 计算其中最大连续1的个数。

在这里插入图片描述

  1. 不需要排序
  2. 快指针i,慢指针j
  3. i每次循环都要自增1,j功能是记录最大连续1的个数,遇到0则清零。
    public int findMaxConsecutiveOnes(int[] nums) {
        int i = 0, j = 0, maxInt = 0;
        // =不能漏,不然最大连续1在最后的话,存不进maxInt
        while (i <= nums.length) {
        	// 顺序不能乱,不然会越界
            if (i < nums.length && nums[i] == 1) {
                i++;
                j++;
            }else {
                // if(j > maxInt){
                //     maxInt = j;
                // }
                maxInt = Math.max(j,maxInt);
                j = 0;
                i++;
            }
        }
        return maxInt;
    }

官方题解:(我发现快指针一般习惯定义在for循环里,比较方便)

  public int findMaxConsecutiveOnes(int[] nums) {
    int count = 0;
    int maxCount = 0;
    for(int i = 0; i < nums.length; i++) {
      if(nums[i] == 1) {
        // Increment the count of 1's by one.
        count += 1;
      } else {
        // Find the maximum till now.
        maxCount = Math.max(maxCount, count);
        // Reset count of 1.
        count = 0;
      }
    }
    return Math.max(maxCount, count);
  }

2.3长度最小的子数组

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

在这里插入图片描述
法1:暴力法

    public int minSubArrayLen(int s, int[] nums) {
        // 暴力法
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int minLen = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum >= s) {
                    minLen = Math.min(minLen, j - i + 1);
                    break;
                }
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }

法2:快慢指针

  1. 不需要排序
  2. 快指针i,慢指针j
  3. 快指针每次循环自增1,慢指针当sum>=s且sum减去nums[j]后自增1,返回长度为i-j+1,是一个滑动窗口。
    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null) return 0;
        int j = 0, sum = 0, minLen = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            while (sum >= s) {
                minLen = minLen == 0 ? i - j + 1 : Math.min(minLen, i - j + 1);
                sum -= nums[j++];
            }
        }
        return minLen;
    }

五、小结

数组相关的技术:

  1. 这里有一些其他类似于数组的数据结构,但具有一些不同的属性:
  1. 正如我们所提到的,我们可以调用内置函数来对数组进行排序。但是,理解一些广泛使用的排序算法的原理及其复杂度是很有用的。
  2. 二分查找也是一种重要的技术,用于在排序数组中搜索特定的元素。
  3. 我们在这一章中引入了双指针技巧。想要灵活运用该技巧是不容易的。这一技巧也可以用来解决:
  1. 双指针技巧有时与贪心算法有关,它可以帮助我们设计指针的移动策略。 我们将会提供更多的卡片来介绍上面提到的这些技术,并更新链接。

1.杨辉三角

118. 杨辉三角

    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> list = new ArrayList<>();
        int[][] arr = new int[numRows][numRows];
        for (int i = 0; i < numRows; i++) {
            List<Integer> subList = new ArrayList<>();
            for (int j = 0; j <= i; j++) {
                if(j == 0 || j == i) {
                    arr[i][j] = 1;
                }else {
                    arr[i][j] = arr[i-1][j-1] + arr[i-1][j];
                }
                subList.add(arr[i][j]);
            }
            list.add(subList);
        }
        return list;
    }

2.杨辉三角Ⅱ

119. 杨辉三角 II
这个是copy了大佬的代码,没认真看,这种题还是留以后再回来做吧。

  public List<Integer> getRow(int rowIndex) {
        Integer[] dp = new Integer[rowIndex + 1];
        Arrays.fill(dp,1);
        for(int i = 2;i < dp.length;i++){
            for(int j = i - 1;j > 0;j--)
                dp[j] = dp[j] + dp[j - 1];
        }
        List<Integer> res = Arrays.asList(dp);
        return res;
    }

3.反转字符串的单词Ⅲ

557. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

在这里插入图片描述

之前在反转字符串没用上的reverse方法,现在可以用上啦。
看题意,只有在每个单词都由相同个空格分隔才可以用这种方法。注意reverse返回的是sb对象。

    public String reverseWords(String s) {
        String[] chars = s.split(" ");
        StringBuffer sb = new StringBuffer();
        for (String str : chars){
            sb.append(new StringBuffer(str).reverse());
            sb.append(" ");
        }
        return sb.toString().trim();// 删去最后的空格
    }

4.寻找旋转排序数组中的最小值

153. 寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。你可以假设数组中不存在重复元素。

在这里插入图片描述

最容易想到的是:

    public int findMin(int[] nums) {
        Arrays.sort(nums);
        return nums[0];
    }

题目提示一开始是已升序的,所以二分查找是很好的选择:

    public int findMin(int nums[]) {
        int l = 0, h = nums.length - 1;
        while (l < h) {
            int mid = l + (h - l) / 2;
            if (nums[mid] > nums[h]) {
                l = mid + 1;
            } else {
                h = mid;
            }
        }
        return nums[l];
    }

5.删除排序数组中的重复项

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

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。


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

在这里插入图片描述

快慢指针:

    public int removeDuplicates(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > nums[j]) {
                nums[++j] = nums[i];
            }
        }
        return j + 1;
    }

6.移动零

283. 移动零

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

这道题我之前做过了,用的是傻傻的方法,现在用快慢指针可以很快做出来:

		public void moveZeroes(int[] nums) {
		if (nums == nul)
            return;
        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;   
       	}

花了几天学习了数组和字符串,内容虽然不算多,但我的收获很多,对数组和字符串题考的内容和做题方法大致掌握了。当然,这只是最简单的题目,更难的题目还在后面等着呢。
LeetBook选的题还是很不错的,整体偏简单,适合我们这种初入算法大门的人做。
下一步决定先快速过一遍数组类算法,然后进入其他数据结构及算法的学习。
https://blog.csdn.net/weixin_42426714/article/details/109244077

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值