LeetCode刷题:数组

Array

练题步骤

  1. 5-10分钟:读题和思考
    • 有思路:自己开始做和写代码;
    • 不然,马上看题解!
  2. 第一遍:默写背诵、熟练
  3. 第二遍:然后开始自己写(闭卷)

移动零

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

题解思路

用两个指针ij,只要nums[i]!=0,我们就交换nums[i]nums[j]

283_2.gif

class Solution {
    public void moveZeroes(int[] nums) {
        // 增强稳健性
        if (nums == null || nums.length == 0)
            return;
        //insertPos就是图示中的b
        int insertPos = 0;
        // i就是图示中的a
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int temp = nums[j];
                nums[insertPos++] = nums[i];
                nums[i] = temp;
            }
        }
    }
}

盛最多水的容器

11. 盛最多水的容器:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

img

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

解题思路一:枚举

  • 枚举:left bar x, right bar y,(x-y) *height_diff
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
// 解法一:枚举法
        int maxArea = 0;
 		// 双层循环的写法
        for (int i = 0; i < height.length; i++) {
            for (int j = i + 1; j < height.length; j++) {
                int area = Math.min(height[i], height[j]) * (j - i);
                maxArea = Math.max(maxArea, area);
            }
        }
        return maxArea;

解题思路二:双指针算法

  • 关键字∶左右两边
  • 模式识别:需要移动左右两头的问题可以考虑双指针
  • 相同情况下两边距离越远越好区域受限于较短边
class Solution {
    public int maxArea(int[] height) {
        if (height.length == 0 || height == null) {
            return 0;
        }
        int left = 0;
        int right = height.length - 1;
        int maxArea = 0;

        while (left < right) {
            int tempArea = Math.min(height[left], height[right]) * (right - left);
            maxArea = Math.max(maxArea, tempArea);
            if (height[left] < height[right]) {
                ++left;
            } else {
                --right;
            }
        }
        return maxArea;
    }
}

精简写法

  • 左右边界i、j,向中间收敛:左右夹逼
class Solution {
    public int maxArea(int[] height) {
        public int maxArea ( int[] height){
            int maxArea = 0;
            for (int i = 0, j = height.length - 1; i < j; ) {
                // 取出小的那个短边
                int minHeight = height[i] < height[j] ? height[i++] : height[j--];
                // 因为这里的i,j在前面被移动过一次,宽度变小,所以要加1;
                // int area = minHeight * (j - i + 1)
                maxArea = Math.max(maxArea,minHeight * (j - i + 1));
            }
            return maxArea;
        }
    }
}

爬楼梯

70. 爬楼梯:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?**注意:**给定 n 是一个正整数。

解题思路一:斐波那契数列

如果观察数学规律,可知本题是斐波那契数列,那么用斐波那契数列的公式即可解决问题,公式如下:根据递推方程 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n - 1) + f(n - 2) f(n)=f(n1)+f(n2),我们可以写出这样的特征方程:
x 2 = x + 1 x^2=x+1 x2=x+1
求得 x 1 = 1 + 5 2 x_1=\frac{1+\sqrt{5}}{2} x1=21+5 x 2 = 1 − 5 2 x_2=\frac{1-\sqrt{5}}{2} x2=215 ,设通解为 f ( n ) = c 1 x 1 n + c 2 x 2 n f(n)=c_1x_{1}^{n} +c_2x_{2}^{n} f(n)=c1x1n+c2x2n,代入初始条件f(1)=1,f(2)=2,得 c 1 = 1 5 c_1=\frac{1}{\sqrt5} c1=5 1, c 2 = − 1 5 c_2=-\frac{1}{\sqrt5} c2=5 1,得到了这个递推数列的通项公式:
F n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F_n=\frac{1}{\sqrt5}[(\frac{1+\sqrt5}{2})^n−(\frac{1-\sqrt5}{2})^n] Fn=5 1[(21+5 )n(215 )n]

  • 时间复杂度:O(logn)
class Solution {
    public int climbStairs(int n) {
        if(n<=0) return 0;
        double sqrt_5=Math.sqrt(5);
        //斐波那契表示为:f(1)=1,f(2) = 1, f(3) = 2....
        //而爬楼梯f(1) = 1, f(2) = 2,f(3) = 3
        //与斐波那契的n表示差1,所以需要将斐波那契数列的n+1
        double fib_n = Math.pow ( (1+sqrt_5)/2 , n+1)+Math.pow((1-sqrt_5)/2 , n+1);
        return (int)(fib_n/sqrt_5);
    }
}

解题思路二:动态规划

  • 找最近重复子问题

本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和

  • 爬上 n-1阶楼梯的方法数量。因为再爬1阶就能到第n阶

  • 爬上 n-2 阶楼梯的方法数量,因为再爬2阶就能到第n阶

所以得到公式 dp[n] = dp[n-1] + dp[n-2]

  • 初始化 dp[0]=1、dp[1]=1和dp[2]=2;

时间复杂度:O(n)

image.png

class Solution {
    public int climbStairs(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
        // dp的下标范围是[0,n]
        int[] dp = new int[n + 1];
        //0阶台阶,没有方式
        dp[0] = 0;
        //1阶台阶,只有一种方式(1)
        dp[1] = 1;
        //2阶台阶,只有两种种方式
        dp[2] = 2;
        //要遍历到第n个台阶,所以指针其实是从[0,n]
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

三数之和

15. 三数之和:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。

解题思路一:暴力求解法

  • 时间复杂度是 O ( n 3 ) O(n^3) O(n3)

大体思路如下,实际上的运行结构并不正确

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //暴力求解法
        Arrays.sort(nums);
        //结果集使用了set集合,避免了返回值重复
        Set<List<Integer>> res = new LinkedHashSet<>();
        for (int i = 0; i < nums.length - 2; i++) {
            for (int j = i + 1; j < nums.length - 1; j++) {
                for (int k = j + 1; k < nums.length; k++) {
                    if (nums[i] + nums[k] + nums[j] == 0) {
                        res.add(Arrays.asList(nums[i], nums[j], nums[k]));
                    }
                }
            }
        }
        return  res;
    }
}

解题思路二:双指针法思路

  • 关键字:不可以包含重复
  • 模式识别:利用排序避免重复答案-降低复杂度变成twoSum
  • 利用双指针找到所有解

双指针法铺垫: 先将给定 nums 排序,复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

具体过程:固定 3 个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引 (k, len(nums))两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:

  1. 特判,对于数组长度 n,如果数组为null或者数组长度小于 3,返回[ ]。
  2. 当 nums[k] > 0 时直接break跳出:因为 nums[j] >= nums[i] >= nums[k] > 0,即 3 个数字都大于 0 ,在此固定指针 k 之后不可能再找到结果了。
  3. 当 k > 0 且 nums[k] == nums[k - 1]时即跳过此元素nums[k]:因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
  4. i,j 分设在数组索引 (k, len(nums)) 两端,当i < j时循环计算s = nums[k] + nums[i] + nums[j],并按照以下规则执行双指针移动:
    • 当s < 0时,i += 1并跳过所有重复的nums[i];
    • 当s > 0时,j -= 1并跳过所有重复的nums[j];
    • 当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。

复杂度分析:

  • 时间复杂度 O ( N 2 ) O(N^2) O(N2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N))。
  • 空间复杂度 O(1):指针使用常数大小的额外空间。

img

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new LinkedList<>();
        for (int k = 0; k < nums.length - 2; k++) {
            // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
            if(nums[k] > 0) break;
            // 当k>0且 nums[k] == nums[k - 1]时即跳过此元素nums[k]
            // k > 0是为了排除初始情况
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            int i = k + 1;
            int j = nums.length - 1;
            while (i < j) {
                int sum = nums[k] + nums[i] + nums[j];
                if (sum < 0) {
                    //当s < 0时,i += 1并跳过所有重复的nums[i];
                    while (i < j && nums[i] == nums[++i]) ;
                } else if (sum > 0) {
                    //当s > 0时,j -= 1并跳过所有重复的nums[j];
                    while (i < j && nums[j] == nums[--j]) ;
                } else {
                    //当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。
                    res.add(Arrays.asList(nums[k], nums[i], nums[j]));
                    while (i < j && nums[i] == nums[++i]) ;
                    while (i < j && nums[j] == nums[--j]) ;
                }
            }

        }
        return res;
    }
}

第三课 数组、链表、跳表-Array

练题步骤

  1. 5-10分钟:读题和思考
    • 有思路:自己开始做和写代码;
    • 不然,马上看题解!
  2. 第一遍:默写背诵、熟练
  3. 第二遍:然后开始自己写(闭卷)

移动零

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

题解思路

用两个指针ij,只要nums[i]!=0,我们就交换nums[i]nums[j]

283_2.gif

class Solution {
    public void moveZeroes(int[] nums) {
        // 增强稳健性
        if (nums == null || nums.length == 0)
            return;
        //insertPos就是图示中的b
        int insertPos = 0;
        // i就是图示中的a
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int temp = nums[j];
                nums[insertPos++] = nums[i];
                nums[i] = temp;
            }
        }
    }
}

盛最多水的容器

11. 盛最多水的容器:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

img

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

解题思路一:枚举

  • 枚举:left bar x, right bar y,(x-y) *height_diff
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
// 解法一:枚举法
        int maxArea = 0;
 		// 双层循环的写法
        for (int i = 0; i < height.length; i++) {
            for (int j = i + 1; j < height.length; j++) {
                int area = Math.min(height[i], height[j]) * (j - i);
                maxArea = Math.max(maxArea, area);
            }
        }
        return maxArea;

解题思路二:双指针算法

  • 关键字∶左右两边
  • 模式识别:需要移动左右两头的问题可以考虑双指针
  • 相同情况下两边距离越远越好区域受限于较短边
class Solution {
    public int maxArea(int[] height) {
        if (height.length == 0 || height == null) {
            return 0;
        }
        int left = 0;
        int right = height.length - 1;
        int maxArea = 0;

        while (left < right) {
            int tempArea = Math.min(height[left], height[right]) * (right - left);
            maxArea = Math.max(maxArea, tempArea);
            if (height[left] < height[right]) {
                ++left;
            } else {
                --right;
            }
        }
        return maxArea;
    }
}

精简写法

  • 左右边界i、j,向中间收敛:左右夹逼
class Solution {
    public int maxArea(int[] height) {
        public int maxArea ( int[] height){
            int maxArea = 0;
            for (int i = 0, j = height.length - 1; i < j; ) {
                // 取出小的那个短边
                int minHeight = height[i] < height[j] ? height[i++] : height[j--];
                // 因为这里的i,j在前面被移动过一次,宽度变小,所以要加1;
                // int area = minHeight * (j - i + 1)
                maxArea = Math.max(maxArea,minHeight * (j - i + 1));
            }
            return maxArea;
        }
    }
}

爬楼梯

70. 爬楼梯:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?**注意:**给定 n 是一个正整数。

解题思路一:斐波那契数列

如果观察数学规律,可知本题是斐波那契数列,那么用斐波那契数列的公式即可解决问题,公式如下:根据递推方程 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n - 1) + f(n - 2) f(n)=f(n1)+f(n2),我们可以写出这样的特征方程:
x 2 = x + 1 x^2=x+1 x2=x+1
求得 x 1 = 1 + 5 2 x_1=\frac{1+\sqrt{5}}{2} x1=21+5 x 2 = 1 − 5 2 x_2=\frac{1-\sqrt{5}}{2} x2=215 ,设通解为 f ( n ) = c 1 x 1 n + c 2 x 2 n f(n)=c_1x_{1}^{n} +c_2x_{2}^{n} f(n)=c1x1n+c2x2n,代入初始条件f(1)=1,f(2)=2,得 c 1 = 1 5 c_1=\frac{1}{\sqrt5} c1=5 1, c 2 = − 1 5 c_2=-\frac{1}{\sqrt5} c2=5 1,得到了这个递推数列的通项公式:
F n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F_n=\frac{1}{\sqrt5}[(\frac{1+\sqrt5}{2})^n−(\frac{1-\sqrt5}{2})^n] Fn=5 1[(21+5 )n(215 )n]

  • 时间复杂度:O(logn)
class Solution {
    public int climbStairs(int n) {
        if(n<=0) return 0;
        double sqrt_5=Math.sqrt(5);
        //斐波那契表示为:f(1)=1,f(2) = 1, f(3) = 2....
        //而爬楼梯f(1) = 1, f(2) = 2,f(3) = 3
        //与斐波那契的n表示差1,所以需要将斐波那契数列的n+1
        double fib_n = Math.pow ( (1+sqrt_5)/2 , n+1)+Math.pow((1-sqrt_5)/2 , n+1);
        return (int)(fib_n/sqrt_5);
    }
}

解题思路二:动态规划

  • 找最近重复子问题

本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和

  • 爬上 n-1阶楼梯的方法数量。因为再爬1阶就能到第n阶

  • 爬上 n-2 阶楼梯的方法数量,因为再爬2阶就能到第n阶

所以得到公式 dp[n] = dp[n-1] + dp[n-2]

  • 初始化 dp[0]=1、dp[1]=1和dp[2]=2;

时间复杂度:O(n)

image.png

class Solution {
    public int climbStairs(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
        // dp的下标范围是[0,n]
        int[] dp = new int[n + 1];
        //0阶台阶,没有方式
        dp[0] = 0;
        //1阶台阶,只有一种方式(1)
        dp[1] = 1;
        //2阶台阶,只有两种种方式
        dp[2] = 2;
        //要遍历到第n个台阶,所以指针其实是从[0,n]
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

三数之和

15. 三数之和:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。

解题思路一:暴力求解法

  • 时间复杂度是 O ( n 3 ) O(n^3) O(n3)

大体思路如下,实际上的运行结构并不正确

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //暴力求解法
        Arrays.sort(nums);
        //结果集使用了set集合,避免了返回值重复
        Set<List<Integer>> res = new LinkedHashSet<>();
        for (int i = 0; i < nums.length - 2; i++) {
            for (int j = i + 1; j < nums.length - 1; j++) {
                for (int k = j + 1; k < nums.length; k++) {
                    if (nums[i] + nums[k] + nums[j] == 0) {
                        res.add(Arrays.asList(nums[i], nums[j], nums[k]));
                    }
                }
            }
        }
        return  res;
    }
}

解题思路二:双指针法思路

  • 关键字:不可以包含重复
  • 模式识别:利用排序避免重复答案-降低复杂度变成twoSum
  • 利用双指针找到所有解

双指针法铺垫: 先将给定 nums 排序,复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

具体过程:固定 3 个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引 (k, len(nums))两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:

  1. 特判,对于数组长度 n,如果数组为null或者数组长度小于 3,返回[ ]。
  2. 当 nums[k] > 0 时直接break跳出:因为 nums[j] >= nums[i] >= nums[k] > 0,即 3 个数字都大于 0 ,在此固定指针 k 之后不可能再找到结果了。
  3. 当 k > 0 且 nums[k] == nums[k - 1]时即跳过此元素nums[k]:因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
  4. i,j 分设在数组索引 (k, len(nums)) 两端,当i < j时循环计算s = nums[k] + nums[i] + nums[j],并按照以下规则执行双指针移动:
    • 当s < 0时,i += 1并跳过所有重复的nums[i];
    • 当s > 0时,j -= 1并跳过所有重复的nums[j];
    • 当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。

复杂度分析:

  • 时间复杂度 O ( N 2 ) O(N^2) O(N2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N))。
  • 空间复杂度 O(1):指针使用常数大小的额外空间。

img

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new LinkedList<>();
        for (int k = 0; k < nums.length - 2; k++) {
            // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
            if(nums[k] > 0) break;
            // 当k>0且 nums[k] == nums[k - 1]时即跳过此元素nums[k]
            // k > 0是为了排除初始情况
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            int i = k + 1;
            int j = nums.length - 1;
            while (i < j) {
                int sum = nums[k] + nums[i] + nums[j];
                if (sum < 0) {
                    //当s < 0时,i += 1并跳过所有重复的nums[i];
                    while (i < j && nums[i] == nums[++i]) ;
                } else if (sum > 0) {
                    //当s > 0时,j -= 1并跳过所有重复的nums[j];
                    while (i < j && nums[j] == nums[--j]) ;
                } else {
                    //当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。
                    res.add(Arrays.asList(nums[k], nums[i], nums[j]));
                    while (i < j && nums[i] == nums[++i]) ;
                    while (i < j && nums[j] == nums[--j]) ;
                }
            }

        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值