LeetCode经典题目:使用ArrayList解法详解 - 实践

适合人群:Java初学者、准备算法面试的同学
内容概述:6道经典LeetCode题目的ArrayList解法详解
⏱️ 阅读时长:约30分钟


目录


题目1:两数之和(Two Sum)

题目描述

LeetCode链接:https://leetcode.cn/problems/two-sum/

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

解题思路

方法1:暴力法(使用ArrayList存储索引)

  • 使用 ArrayList 存储数组元素及其索引
  • 双重循环遍历,查找两个数的和等于 target
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

方法2:优化版(ArrayList + 线性查找)

  • 使用 ArrayList 存储已遍历的元素
  • 对于每个元素,计算 complement = target - nums[i]
  • ArrayList 中查找是否存在 complement
  • 时间复杂度:O(n²)(因为 ArrayList.contains() 是 O(n))
  • 空间复杂度:O(n)

代码实现

import java.util.ArrayList;
import java.util.List;
/**
* LeetCode 1. 两数之和
* 使用 ArrayList 解法
*/
public class TwoSum {
/**
* 方法1:使用 ArrayList 存储元素和索引的对应关系
*
* 思路:
* 1. 创建一个 ArrayList 存储数组元素和索引
* 2. 双重循环遍历,查找两个数的和等于 target
* 3. 返回这两个数的索引
*
* @param nums   整数数组
* @param target 目标值
* @return 两个数的索引数组
*/
public int[] twoSum1(int[] nums, int target) {
// 创建一个列表,存储数组元素和索引的对应关系
// 使用 int[] 数组存储 [元素值, 索引]
List<int[]> list = new ArrayList<>();
  // 第一步:将数组元素和索引存储到 ArrayList 中
  for (int i = 0; i < nums.length; i++) {
  list.add(new int[]{nums[i], i});  // [元素值, 索引]
  }
  // 第二步:双重循环查找两个数的和等于 target
  for (int i = 0; i < list.size(); i++) {
  for (int j = i + 1; j < list.size(); j++) {
  int num1 = list.get(i)[0];  // 获取第一个元素的值
  int num2 = list.get(j)[0];  // 获取第二个元素的值
  // 如果两个数的和等于 target,返回它们的索引
  if (num1 + num2 == target) {
  return new int[]{list.get(i)[1], list.get(j)[1]};
  }
  }
  }
  // 如果没有找到,返回空数组
  return new int[]{};
  }
  /**
  * 方法2:使用 ArrayList 存储已遍历的元素(优化思路,但时间复杂度仍是 O(n²))
  *
  * 思路:
  * 1. 遍历数组,对于每个元素计算 complement = target - nums[i]
  * 2. 在 ArrayList 中查找是否存在 complement
  * 3. 如果存在,返回对应的索引
  *
  * @param nums   整数数组
  * @param target 目标值
  * @return 两个数的索引数组
  */
  public int[] twoSum2(int[] nums, int target) {
  // 创建一个列表存储已遍历的元素值
  List<Integer> numList = new ArrayList<>();
    // 遍历数组
    for (int i = 0; i < nums.length; i++) {
    // 计算需要的补数(complement)
    int complement = target - nums[i];
    // 在 ArrayList 中查找是否存在补数
    if (numList.contains(complement)) {
    // 如果存在,返回补数的索引和当前索引
    int complementIndex = numList.indexOf(complement);
    return new int[]{complementIndex, i};
    }
    // 将当前元素添加到列表中
    numList.add(nums[i]);
    }
    // 如果没有找到,抛出异常
    throw new IllegalArgumentException("No two sum solution");
    }
    /**
    * 测试方法
    */
    public static void main(String[] args) {
    TwoSum solution = new TwoSum();
    // 测试用例1
    System.out.println("========== 测试用例1 ==========");
    int[] nums1 = {2, 7, 11, 15};
    int target1 = 9;
    int[] result1 = solution.twoSum1(nums1, target1);
    System.out.println("输入:nums = [2,7,11,15], target = 9");
    System.out.println("输出:[" + result1[0] + "," + result1[1] + "]");
    System.out.println("预期:[0,1]");
    // 测试用例2
    System.out.println("\n========== 测试用例2 ==========");
    int[] nums2 = {3, 2, 4};
    int target2 = 6;
    int[] result2 = solution.twoSum1(nums2, target2);
    System.out.println("输入:nums = [3,2,4], target = 6");
    System.out.println("输出:[" + result2[0] + "," + result2[1] + "]");
    System.out.println("预期:[1,2]");
    // 测试用例3
    System.out.println("\n========== 测试用例3 ==========");
    int[] nums3 = {3, 3};
    int target3 = 6;
    int[] result3 = solution.twoSum1(nums3, target3);
    System.out.println("输入:nums = [3,3], target = 6");
    System.out.println("输出:[" + result3[0] + "," + result3[1] + "]");
    System.out.println("预期:[0,1]");
    }
    }

复杂度分析

方法时间复杂度空间复杂度说明
方法1O(n²)O(n)双重循环 + ArrayList存储
方法2O(n²)O(n)ArrayList.contains() 是 O(n)

优化建议

面试官可能会问:如何优化到 O(n) 时间复杂度?

答案:使用 HashMap 而不是 ArrayList,因为 HashMap.containsKey() 是 O(1) 时间复杂度。

// 优化版本(使用 HashMap,时间复杂度 O(n))
import java.util.HashMap;
import java.util.Map;
public int[] twoSumOptimal(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
  for (int i = 0; i < nums.length; i++) {
  int complement = target - nums[i];
  if (map.containsKey(complement)) {
  return new int[]{map.get(complement), i};
  }
  map.put(nums[i], i);
  }
  throw new IllegalArgumentException("No two sum solution");
  }

题目2:合并两个有序数组(Merge Sorted Array)

题目描述

LeetCode链接:https://leetcode.cn/problems/merge-sorted-array/description/

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn,分别表示 nums1nums2 中元素的数目。

请你 合并nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0,应忽略。nums2 的长度为 n

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

解题思路

双指针 + ArrayList 方法

  1. 创建 ArrayList 存储合并后的结果
  2. 使用两个指针 ij 分别指向 nums1nums2 的起始位置
  3. 比较两个数组当前元素,将较小的元素加入 ArrayList
  4. 将剩余元素加入 ArrayList
  5. ArrayList 中的元素复制回 nums1

代码实现

import java.util.ArrayList;
import java.util.List;
/**
* LeetCode 88. 合并两个有序数组
* 使用 ArrayList 解法
*/
public class MergeSortedArray {
/**
* 合并两个有序数组
*
* 思路:
* 1. 使用 ArrayList 存储合并后的结果
* 2. 双指针法:同时遍历 nums1 和 nums2
* 3. 比较当前元素,将较小的加入 ArrayList
* 4. 处理剩余元素
* 5. 将结果复制回 nums1
*
* @param nums1 第一个有序数组(长度为 m+n,前 m 个元素有效)
* @param m     nums1 中有效元素个数
* @param nums2 第二个有序数组
* @param n     nums2 中元素个数
*/
public void merge(int[] nums1, int m, int[] nums2, int n) {
// 第一步:创建 ArrayList 存储合并后的结果
List<Integer> result = new ArrayList<>();
  // 第二步:定义两个指针,分别指向 nums1 和 nums2 的起始位置
  int i = 0;  // nums1 的指针(只遍历前 m 个有效元素)
  int j = 0;  // nums2 的指针
  // 第三步:双指针比较,将较小的元素加入 ArrayList
  while (i < m && j < n) {
  if (nums1[i] <= nums2[j]) {
  // nums1 的元素较小或相等,加入结果
  result.add(nums1[i]);
  i++;  // nums1 指针后移
  } else {
  // nums2 的元素较小,加入结果
  result.add(nums2[j]);
  j++;  // nums2 指针后移
  }
  }
  // 第四步:处理 nums1 的剩余元素(如果还有)
  while (i < m) {
  result.add(nums1[i]);
  i++;
  }
  // 第五步:处理 nums2 的剩余元素(如果还有)
  while (j < n) {
  result.add(nums2[j]);
  j++;
  }
  // 第六步:将 ArrayList 中的元素复制回 nums1
  for (int k = 0; k < result.size(); k++) {
  nums1[k] = result.get(k);
  }
  }
  /**
  * 测试方法
  */
  public static void main(String[] args) {
  MergeSortedArray solution = new MergeSortedArray();
  // 测试用例1
  System.out.println("========== 测试用例1 ==========");
  int[] nums1_1 = {1, 2, 3, 0, 0, 0};
  int m1 = 3;
  int[] nums2_1 = {2, 5, 6};
  int n1 = 3;
  System.out.println("合并前:");
  System.out.print("nums1 = [");
  for (int i = 0; i < m1; i++) {
  System.out.print(nums1_1[i]);
  if (i < m1 - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("nums2 = [2,5,6]");
  solution.merge(nums1_1, m1, nums2_1, n1);
  System.out.println("合并后:");
  System.out.print("nums1 = [");
  for (int i = 0; i < nums1_1.length; i++) {
  System.out.print(nums1_1[i]);
  if (i < nums1_1.length - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:[1,2,2,3,5,6]");
  // 测试用例2
  System.out.println("\n========== 测试用例2 ==========");
  int[] nums1_2 = {1};
  int m2 = 1;
  int[] nums2_2 = {};
  int n2 = 0;
  solution.merge(nums1_2, m2, nums2_2, n2);
  System.out.println("合并后:nums1 = [" + nums1_2[0] + "]");
  System.out.println("预期:[1]");
  // 测试用例3
  System.out.println("\n========== 测试用例3 ==========");
  int[] nums1_3 = {0};
  int m3 = 0;
  int[] nums2_3 = {1};
  int n3 = 1;
  solution.merge(nums1_3, m3, nums2_3, n3);
  System.out.println("合并后:nums1 = [" + nums1_3[0] + "]");
  System.out.println("预期:[1]");
  }
  }

复杂度分析

指标复杂度说明
时间复杂度O(m + n)需要遍历两个数组的所有元素
空间复杂度O(m + n)使用 ArrayList 存储合并结果

算法流程示例

示例:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
步骤1: i=0, j=0
  nums1[0]=1, nums2[0]=2 → 1 < 2,result.add(1)
  result = [1]
步骤2: i=1, j=0
  nums1[1]=2, nums2[0]=2 → 2 == 2,result.add(2)
  result = [1, 2]
步骤3: i=2, j=0
  nums1[2]=3, nums2[0]=2 → 3 > 2,result.add(2)
  result = [1, 2, 2]
步骤4: i=2, j=1
  nums1[2]=3, nums2[1]=5 → 3 < 5,result.add(3)
  result = [1, 2, 2, 3]
步骤5: i=3 >= m,nums1 遍历完,将 nums2 剩余元素加入
  result.add(5), result.add(6)
  result = [1, 2, 2, 3, 5, 6]
最后:将 result 复制回 nums1
  nums1 = [1, 2, 2, 3, 5, 6]

题目3:移除元素(Remove Element)

题目描述

LeetCode链接:https://leetcode.cn/problems/remove-element/description/

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

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

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。

解题思路

ArrayList 方法

  1. 创建 ArrayList 存储不等于 val 的元素
  2. 遍历原数组,将不等于 val 的元素加入 ArrayList
  3. ArrayList 中的元素复制回原数组
  4. 返回 ArrayList 的大小

注意:虽然题目要求 O(1) 空间复杂度,但这里使用 ArrayList 是为了演示 ArrayList 的用法。实际最优解是双指针法。

代码实现

import java.util.ArrayList;
import java.util.List;
/**
* LeetCode 27. 移除元素
* 使用 ArrayList 解法
*/
public class RemoveElement {
/**
* 移除数组中所有等于 val 的元素
*
* 思路:
* 1. 使用 ArrayList 存储不等于 val 的元素
* 2. 遍历原数组,筛选出不等于 val 的元素
* 3. 将筛选后的元素复制回原数组
* 4. 返回新数组的长度
*
* @param nums 整数数组
* @param val  要移除的值
* @return 移除后数组的新长度
*/
public int removeElement(int[] nums, int val) {
// 第一步:创建 ArrayList 存储不等于 val 的元素
List<Integer> result = new ArrayList<>();
  // 第二步:遍历原数组,筛选出不等于 val 的元素
  for (int num : nums) {
  if (num != val) {
  // 如果当前元素不等于 val,加入 ArrayList
  result.add(num);
  }
  }
  // 第三步:将 ArrayList 中的元素复制回原数组
  for (int i = 0; i < result.size(); i++) {
  nums[i] = result.get(i);
  }
  // 第四步:返回新数组的长度
  return result.size();
  }
  /**
  * 测试方法
  */
  public static void main(String[] args) {
  RemoveElement solution = new RemoveElement();
  // 测试用例1
  System.out.println("========== 测试用例1 ==========");
  int[] nums1 = {3, 2, 2, 3};
  int val1 = 3;
  System.out.println("输入:nums = [3,2,2,3], val = 3");
  int length1 = solution.removeElement(nums1, val1);
  System.out.println("输出:" + length1);
  System.out.print("nums = [");
  for (int i = 0; i < length1; i++) {
  System.out.print(nums1[i]);
  if (i < length1 - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:2, nums = [2,2]");
  // 测试用例2
  System.out.println("\n========== 测试用例2 ==========");
  int[] nums2 = {0, 1, 2, 2, 3, 0, 4, 2};
  int val2 = 2;
  System.out.println("输入:nums = [0,1,2,2,3,0,4,2], val = 2");
  int length2 = solution.removeElement(nums2, val2);
  System.out.println("输出:" + length2);
  System.out.print("nums = [");
  for (int i = 0; i < length2; i++) {
  System.out.print(nums2[i]);
  if (i < length2 - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:5, nums = [0,1,3,0,4]");
  // 测试用例3
  System.out.println("\n========== 测试用例3 ==========");
  int[] nums3 = {1};
  int val3 = 1;
  System.out.println("输入:nums = [1], val = 1");
  int length3 = solution.removeElement(nums3, val3);
  System.out.println("输出:" + length3);
  System.out.println("预期:0");
  }
  }

复杂度分析

指标复杂度说明
时间复杂度O(n)需要遍历数组一次
空间复杂度O(n)使用 ArrayList 存储结果(不符合题目要求,但演示了 ArrayList 用法)

优化版本(双指针法,O(1) 空间复杂度)

/**
* 优化版本:双指针法(符合题目要求的 O(1) 空间复杂度)
*/
public int removeElementOptimal(int[] nums, int val) {
// 慢指针:指向下一个不等于 val 的元素应该放置的位置
int slow = 0;
// 快指针:遍历数组
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
// 如果当前元素不等于 val,将其放到慢指针位置
nums[slow] = nums[fast];
slow++;  // 慢指针后移
}
}
return slow;  // 返回新数组的长度
}

题目4:找到所有数组中消失的数字(Find All Numbers Disappeared)

题目描述

LeetCode链接:https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/

给你一个含 n 个整数的数组 nums,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
解释:数组长度为 8,数字应该在 [1,8] 范围内
      出现的数字:1,2,3,4,7,8
      缺失的数字:5,6

示例 2:

输入:nums = [1,1]
输出:[2]
解释:数组长度为 2,数字应该在 [1,2] 范围内
      出现的数字:1
      缺失的数字:2

解题思路

方法1:使用 HashSet(推荐)

  1. 使用 HashSet 存储数组中出现的所有数字
  2. 遍历 [1, n],找出不在 HashSet 中的数字
  3. 将缺失的数字加入 ArrayList 返回

方法2:使用 ArrayList + 标记法

  1. 创建一个布尔数组标记数字是否出现
  2. 遍历 nums,将出现的数字标记为 true
  3. 遍历标记数组,找出未标记的数字

代码实现

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* LeetCode 448. 找到所有数组中消失的数字
* 使用 ArrayList 和 HashSet 解法
*/
public class FindDisappearedNumbers {
/**
* 方法1:使用 HashSet 存储出现的数字
*
* 思路:
* 1. 使用 HashSet 存储数组中出现的所有数字(O(1) 查找)
* 2. 遍历 [1, n],找出不在 HashSet 中的数字
* 3. 将缺失的数字加入 ArrayList 返回
*
* @param nums 整数数组
* @return 缺失的数字列表
*/
public List<Integer> findDisappearedNumbers1(int[] nums) {
  // 第一步:创建 HashSet 存储数组中出现的数字
  Set<Integer> numSet = new HashSet<>();
    // 第二步:遍历数组,将出现的数字加入 HashSet
    for (int num : nums) {
    numSet.add(num);
    }
    // 第三步:创建 ArrayList 存储缺失的数字
    List<Integer> result = new ArrayList<>();
      // 第四步:遍历 [1, n],找出不在 HashSet 中的数字
      for (int i = 1; i <= nums.length; i++) {
      if (!numSet.contains(i)) {
      // 如果数字 i 不在 HashSet 中,说明它缺失了
      result.add(i);
      }
      }
      return result;
      }
      /**
      * 方法2:使用 ArrayList + 布尔数组标记
      *
      * 思路:
      * 1. 创建一个布尔数组,标记数字是否出现
      * 2. 遍历 nums,将出现的数字标记为 true
      * 3. 遍历标记数组,找出未标记的数字
      *
      * @param nums 整数数组
      * @return 缺失的数字列表
      */
      public List<Integer> findDisappearedNumbers2(int[] nums) {
        // 第一步:创建布尔数组,标记数字是否出现
        // present[i] = true 表示数字 i+1 出现过
        boolean[] present = new boolean[nums.length + 1];
        // 第二步:遍历数组,将出现的数字标记为 true
        for (int num : nums) {
        present[num] = true;  // 数字 num 出现过
        }
        // 第三步:创建 ArrayList 存储缺失的数字
        List<Integer> result = new ArrayList<>();
          // 第四步:遍历 [1, n],找出未标记的数字
          for (int i = 1; i < present.length; i++) {
          if (!present[i]) {
          // 如果数字 i 未标记,说明它缺失了
          result.add(i);
          }
          }
          return result;
          }
          /**
          * 方法3:原地标记法(空间复杂度 O(1))
          *
          * 思路:
          * 1. 遍历数组,将数字对应索引位置的数字变为负数(标记)
          * 2. 再次遍历数组,找出仍为正数的位置,其索引+1 就是缺失的数字
          *
          * @param nums 整数数组
          * @return 缺失的数字列表
          */
          public List<Integer> findDisappearedNumbers3(int[] nums) {
            // 第一步:遍历数组,将数字对应索引位置的数字变为负数
            for (int num : nums) {
            // 获取数字对应的索引(因为数字范围是 [1, n],索引范围是 [0, n-1])
            int index = Math.abs(num) - 1;
            // 如果该位置的数字是正数,将其变为负数(标记为已出现)
            if (nums[index] > 0) {
            nums[index] = -nums[index];
            }
            }
            // 第二步:创建 ArrayList 存储缺失的数字
            List<Integer> result = new ArrayList<>();
              // 第三步:遍历数组,找出仍为正数的位置
              for (int i = 0; i < nums.length; i++) {
              if (nums[i] > 0) {
              // 如果该位置的数字仍为正数,说明数字 i+1 缺失了
              result.add(i + 1);
              }
              }
              return result;
              }
              /**
              * 测试方法
              */
              public static void main(String[] args) {
              FindDisappearedNumbers solution = new FindDisappearedNumbers();
              // 测试用例1
              System.out.println("========== 测试用例1 ==========");
              int[] nums1 = {4, 3, 2, 7, 8, 2, 3, 1};
              System.out.println("输入:nums = [4,3,2,7,8,2,3,1]");
              List<Integer> result1 = solution.findDisappearedNumbers1(nums1);
                System.out.print("输出:[");
                for (int i = 0; i < result1.size(); i++) {
                System.out.print(result1.get(i));
                if (i < result1.size() - 1) System.out.print(",");
                }
                System.out.println("]");
                System.out.println("预期:[5,6]");
                // 测试用例2
                System.out.println("\n========== 测试用例2 ==========");
                int[] nums2 = {1, 1};
                System.out.println("输入:nums = [1,1]");
                List<Integer> result2 = solution.findDisappearedNumbers1(nums2);
                  System.out.print("输出:[");
                  for (int i = 0; i < result2.size(); i++) {
                  System.out.print(result2.get(i));
                  if (i < result2.size() - 1) System.out.print(",");
                  }
                  System.out.println("]");
                  System.out.println("预期:[2]");
                  }
                  }

复杂度分析

方法时间复杂度空间复杂度说明
方法1(HashSet)O(n)O(n)使用 HashSet 存储,查找 O(1)
方法2(布尔数组)O(n)O(n)使用布尔数组标记
方法3(原地标记)O(n)O(1)原地修改数组,最优解

题目5:加一(Plus One)

题目描述

LeetCode链接:https://leetcode.cn/problems/plus-one/

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123,加一后为 124。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321,加一后为 4322。

示例 3:

输入:digits = [0]
输出:[1]

示例 4:

输入:digits = [9,9,9]
输出:[1,0,0,0]
解释:输入数组表示数字 999,加一后为 1000,需要增加一位。

解题思路

ArrayList 方法

  1. 将数组转换为 ArrayList(方便处理)
  2. 从数组末尾开始,模拟加一操作
  3. 处理进位:如果某位加一后 >= 10,需要进位
  4. 如果最高位还有进位,需要在数组前添加 1
  5. ArrayList 转换回数组返回

代码实现

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* LeetCode 66. 加一
* 使用 ArrayList 解法
*/
public class PlusOne {
/**
* 对数组表示的数字加一
*
* 思路:
* 1. 将数组转换为 ArrayList(方便在头部插入)
* 2. 从末尾开始,模拟加一操作
* 3. 处理进位:如果某位 >= 10,需要进位
* 4. 如果最高位还有进位,在数组前添加 1
* 5. 将 ArrayList 转换回数组
*
* @param digits 表示数字的数组
* @return 加一后的数组
*/
public int[] plusOne(int[] digits) {
// 第一步:将数组转换为 ArrayList(方便处理)
List<Integer> list = new ArrayList<>();
  for (int digit : digits) {
  list.add(digit);
  }
  // 第二步:从数组末尾开始,模拟加一操作
  int carry = 1;  // 初始进位为 1(表示加一)
  for (int i = list.size() - 1; i >= 0; i--) {
  // 当前位的值 = 原值 + 进位
  int sum = list.get(i) + carry;
  // 更新当前位:sum % 10 得到个位数
  list.set(i, sum % 10);
  // 更新进位:sum / 10 得到十位数(进位)
  carry = sum / 10;
  // 如果进位为 0,说明不需要继续进位,可以提前结束
  if (carry == 0) {
  break;
  }
  }
  // 第三步:如果最高位还有进位,需要在数组前添加 1
  if (carry > 0) {
  list.add(0, 1);  // 在索引 0 的位置插入 1
  }
  // 第四步:将 ArrayList 转换回数组
  int[] result = new int[list.size()];
  for (int i = 0; i < list.size(); i++) {
  result[i] = list.get(i);
  }
  return result;
  }
  /**
  * 优化版本:直接操作数组(不使用 ArrayList)
  *
  * @param digits 表示数字的数组
  * @return 加一后的数组
  */
  public int[] plusOneOptimal(int[] digits) {
  // 从末尾开始遍历
  for (int i = digits.length - 1; i >= 0; i--) {
  // 当前位加一
  digits[i]++;
  // 如果当前位 < 10,不需要进位,直接返回
  if (digits[i] < 10) {
  return digits;
  }
  // 如果当前位 >= 10,需要进位
  digits[i] = 0;  // 当前位变为 0
  }
  // 如果所有位都进位了,需要增加一位
  // 例如:[9,9,9] → [1,0,0,0]
  int[] result = new int[digits.length + 1];
  result[0] = 1;  // 最高位为 1,其他位为 0
  return result;
  }
  /**
  * 测试方法
  */
  public static void main(String[] args) {
  PlusOne solution = new PlusOne();
  // 测试用例1
  System.out.println("========== 测试用例1 ==========");
  int[] digits1 = {1, 2, 3};
  System.out.println("输入:digits = [1,2,3]");
  int[] result1 = solution.plusOne(digits1);
  System.out.print("输出:[");
  for (int i = 0; i < result1.length; i++) {
  System.out.print(result1[i]);
  if (i < result1.length - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:[1,2,4]");
  // 测试用例2
  System.out.println("\n========== 测试用例2 ==========");
  int[] digits2 = {4, 3, 2, 1};
  System.out.println("输入:digits = [4,3,2,1]");
  int[] result2 = solution.plusOne(digits2);
  System.out.print("输出:[");
  for (int i = 0; i < result2.length; i++) {
  System.out.print(result2[i]);
  if (i < result2.length - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:[4,3,2,2]");
  // 测试用例3
  System.out.println("\n========== 测试用例3 ==========");
  int[] digits3 = {0};
  System.out.println("输入:digits = [0]");
  int[] result3 = solution.plusOne(digits3);
  System.out.print("输出:[");
  for (int i = 0; i < result3.length; i++) {
  System.out.print(result3[i]);
  if (i < result3.length - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:[1]");
  // 测试用例4(需要增加位数)
  System.out.println("\n========== 测试用例4 ==========");
  int[] digits4 = {9, 9, 9};
  System.out.println("输入:digits = [9,9,9]");
  int[] result4 = solution.plusOne(digits4);
  System.out.print("输出:[");
  for (int i = 0; i < result4.length; i++) {
  System.out.print(result4[i]);
  if (i < result4.length - 1) System.out.print(",");
  }
  System.out.println("]");
  System.out.println("预期:[1,0,0,0]");
  }
  }

复杂度分析

指标复杂度说明
时间复杂度O(n)最坏情况需要遍历整个数组
空间复杂度O(n)使用 ArrayList 存储(优化版本为 O(1))

算法流程示例

示例1:digits = [1,2,3]
  步骤1: 从末尾开始,3 + 1 = 4,无需进位
  结果: [1,2,4]
示例2:digits = [9,9,9]
  步骤1: 9 + 1 = 10,个位为 0,进位为 1
  步骤2: 9 + 1 = 10,个位为 0,进位为 1
  步骤3: 9 + 1 = 10,个位为 0,进位为 1
  步骤4: 最高位还有进位,添加 1
  结果: [1,0,0,0]

题目6:杨辉三角(Pascal’s Triangle)

题目描述

LeetCode链接:https://leetcode.cn/problems/pascals-triangle/

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:

输入: numRows = 1
输出: [[1]]

解题思路

杨辉三角的性质

  1. 第一行只有 1 个元素:[1]
  2. 第二行有 2 个元素:[1, 1]
  3. i 行(从 1 开始)有 i 个元素
  4. 每行的第一个和最后一个元素都是 1
  5. 中间的元素:triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]

ArrayList 方法

  1. 创建一个 ArrayList 存储所有行(每行也是一个 ArrayList
  2. 生成第一行:[1]
  3. 从第二行开始,利用上一行生成当前行
  4. 返回结果

代码实现

import java.util.ArrayList;
import java.util.List;
/**
* LeetCode 118. 杨辉三角
* 使用 ArrayList 解法
*/
public class PascalsTriangle {
/**
* 生成杨辉三角的前 numRows 行
*
* 思路:
* 1. 创建一个 ArrayList 存储所有行
* 2. 第一行:只有一个元素 [1]
* 3. 从第二行开始:
*    - 每行的第一个元素是 1
*    - 中间元素 = 上一行的前一个元素 + 上一行的当前元素
*    - 每行的最后一个元素是 1
*
* @param numRows 行数
* @return 杨辉三角的所有行
*/
public List<List<Integer>> generate(int numRows) {
  // 第一步:创建 ArrayList 存储所有行
  List<List<Integer>> triangle = new ArrayList<>();
    // 如果行数为 0,直接返回空列表
    if (numRows == 0) {
    return triangle;
    }
    // 第二步:生成第一行 [1]
    List<Integer> firstRow = new ArrayList<>();
      firstRow.add(1);
      triangle.add(firstRow);
      // 第三步:从第二行开始生成(索引从 1 开始)
      for (int rowNum = 1; rowNum < numRows; rowNum++) {
      // 创建当前行
      List<Integer> currentRow = new ArrayList<>();
        // 获取上一行
        List<Integer> prevRow = triangle.get(rowNum - 1);
          // 当前行的第一个元素是 1
          currentRow.add(1);
          // 生成中间元素(从第二个元素到倒数第二个元素)
          // 中间元素 = 上一行的前一个元素 + 上一行的当前元素
          for (int j = 1; j < rowNum; j++) {
          int sum = prevRow.get(j - 1) + prevRow.get(j);
          currentRow.add(sum);
          }
          // 当前行的最后一个元素是 1
          currentRow.add(1);
          // 将当前行添加到 triangle
          triangle.add(currentRow);
          }
          return triangle;
          }
          /**
          * 打印杨辉三角(美化输出)
          *
          * @param triangle 杨辉三角
          */
          public void printTriangle(List<List<Integer>> triangle) {
            for (int i = 0; i < triangle.size(); i++) {
            // 打印前导空格(居中显示)
            for (int k = 0; k < triangle.size() - i - 1; k++) {
            System.out.print("  ");
            }
            // 打印当前行的元素
            List<Integer> row = triangle.get(i);
              for (int j = 0; j < row.size(); j++) {
              System.out.print(row.get(j));
              if (j < row.size() - 1) {
              System.out.print("   ");
              }
              }
              System.out.println();
              }
              }
              /**
              * 测试方法
              */
              public static void main(String[] args) {
              PascalsTriangle solution = new PascalsTriangle();
              // 测试用例1
              System.out.println("========== 测试用例1:numRows = 5 ==========");
              List<List<Integer>> triangle1 = solution.generate(5);
                System.out.println("输出:");
                for (List<Integer> row : triangle1) {
                  System.out.println(row);
                  }
                  System.out.println("预期:[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]");
                  System.out.println("\n美化输出:");
                  solution.printTriangle(triangle1);
                  /*
                  * 输出:
                  *         1
                  *       1   1
                  *     1   2   1
                  *   1   3   3   1
                  * 1   4   6   4   1
                  */
                  // 测试用例2
                  System.out.println("\n========== 测试用例2:numRows = 1 ==========");
                  List<List<Integer>> triangle2 = solution.generate(1);
                    System.out.println("输出:");
                    for (List<Integer> row : triangle2) {
                      System.out.println(row);
                      }
                      System.out.println("预期:[[1]]");
                      // 测试用例3
                      System.out.println("\n========== 测试用例3:numRows = 6 ==========");
                      List<List<Integer>> triangle3 = solution.generate(6);
                        System.out.println("美化输出:");
                        solution.printTriangle(triangle3);
                        }
                        }

复杂度分析

指标复杂度说明
时间复杂度O(numRows²)需要生成 numRows 行,每行最多有 numRows 个元素
空间复杂度O(numRows²)需要存储所有行的元素

杨辉三角的性质

杨辉三角示例(5行):
        1
       1 1
      1 2 1
     1 3 3 1
    1 4 6 4 1
性质:
1. 第 n 行有 n 个元素
2. 每行的第一个和最后一个元素都是 1
3. 中间元素 = 上一行的前一个元素 + 上一行的当前元素
   例如:第 4 行的第 2 个元素 3 = 第 3 行的第 1 个元素 1 + 第 3 行的第 2 个元素 2

总结与面试题

六道题目总结

题目难度核心知识点ArrayList 的作用
两数之和简单哈希表、双指针存储元素和索引(可优化为 HashMap)
合并有序数组简单双指针、归并存储合并结果
移除元素简单双指针、原地修改存储筛选后的元素
消失的数字简单哈希表、标记法存储结果
加一简单数组、进位方便处理进位和插入
杨辉三角简单动态规划、递推存储每行的元素

常见面试问题

1. ArrayList 和数组的区别?

答案

  • 数组:长度固定,创建后不能改变
  • ArrayList:长度可变,自动扩容
  • 数组:可以存储基本类型和引用类型
  • ArrayList:只能存储引用类型(基本类型需要包装类)
// 数组
int[] arr = new int[10];  // 长度固定
// ArrayList
ArrayList<Integer> list = new ArrayList<>();  // 长度可变
  list.add(1);  // 自动扩容
2. ArrayList 的扩容机制?

答案

  • 初始容量:10
  • 扩容时机:元素数量超过当前容量
  • 扩容大小:新容量 = 旧容量 × 1.5
  • 扩容步骤:创建新数组 → 复制元素 → 更新引用
3. ArrayList 的时间复杂度?

答案

操作时间复杂度说明
add()O(1) 平均尾部添加,最坏 O(n)(需要扩容)
add(index, element)O(n)需要移动元素
get(index)O(1)随机访问
remove(index)O(n)需要移动元素
contains()O(n)需要遍历查找
4. 什么时候用 ArrayList,什么时候用数组?

答案

  • 用 ArrayList
    • 需要动态扩容
    • 不确定元素数量
    • 需要频繁添加/删除元素(尾部操作)
  • 用数组
    • 长度固定
    • 性能要求高(无额外开销)
    • 需要存储基本类型
5. 如何优化 ArrayList 的性能?

答案

  1. 指定初始容量:如果知道大概大小,使用 new ArrayList<>(initialCapacity)
  2. 避免频繁扩容:预估容量,减少扩容次数
  3. 尾部操作:尽量在尾部添加/删除元素
  4. 批量操作:使用 addAll() 而不是循环 add()
// 优化前
ArrayList<Integer> list = new ArrayList<>();
  for (int i = 0; i < 1000; i++) {
  list.add(i);  // 可能多次扩容
  }
  // 优化后
  ArrayList<Integer> list = new ArrayList<>(1000);  // 指定初始容量
    for (int i = 0; i < 1000; i++) {
    list.add(i);  // 不需要扩容
    }

学习建议

  1. 理解原理:掌握 ArrayList 的底层实现(数组 + 扩容)
  2. 熟练使用:多练习 ArrayList 的常用方法
  3. 性能优化:了解时间复杂度,选择合适的操作
  4. 实际应用:结合 LeetCode 题目练习

相关资源

  • LeetCode 官网:https://leetcode.cn/
  • Java ArrayList 文档:https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
  • 算法可视化:https://visualgo.net/

结语:通过这 6 道 LeetCode 题目,我们学习了 ArrayList 的各种用法,包括存储、遍历、筛选、合并等操作。希望这篇文章能帮助大家更好地理解和掌握 ArrayList!

推荐阅读:Java 集合 List 详解、Java 集合 Map 详解、Java 集合 Set 详解
相关文章:LeetCode 刷题指南、Java 数据结构与算法


posted @ 2025-12-05 22:23  clnchanpin  阅读(10)  评论(0)    收藏  举报