LeetCode经典题目:使用ArrayList解法详解 - 实践
适合人群:Java初学者、准备算法面试的同学
内容概述:6道经典LeetCode题目的ArrayList解法详解
⏱️ 阅读时长:约30分钟
目录
- 题目1:两数之和(Two Sum)
- 题目2:合并两个有序数组(Merge Sorted Array)
- 题目3:移除元素(Remove Element)
- 题目4:找到所有数组中消失的数字(Find All Numbers Disappeared)
- 题目5:加一(Plus One)
- 题目6:杨辉三角(Pascal’s Triangle)
- 总结与面试题
题目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]");
}
}
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 方法1 | O(n²) | O(n) | 双重循环 + ArrayList存储 |
| 方法2 | O(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/
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n,分别表示 nums1 和 nums2 中元素的数目。
请你 合并nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 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 方法:
- 创建
ArrayList存储合并后的结果 - 使用两个指针
i和j分别指向nums1和nums2的起始位置 - 比较两个数组当前元素,将较小的元素加入
ArrayList - 将剩余元素加入
ArrayList - 将
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 方法:
- 创建
ArrayList存储不等于val的元素 - 遍历原数组,将不等于
val的元素加入ArrayList - 将
ArrayList中的元素复制回原数组 - 返回
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(推荐)
- 使用
HashSet存储数组中出现的所有数字 - 遍历
[1, n],找出不在HashSet中的数字 - 将缺失的数字加入
ArrayList返回
方法2:使用 ArrayList + 标记法
- 创建一个布尔数组标记数字是否出现
- 遍历
nums,将出现的数字标记为true - 遍历标记数组,找出未标记的数字
代码实现
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 方法:
- 将数组转换为
ArrayList(方便处理) - 从数组末尾开始,模拟加一操作
- 处理进位:如果某位加一后 >= 10,需要进位
- 如果最高位还有进位,需要在数组前添加 1
- 将
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] - 第二行有 2 个元素:
[1, 1] - 第
i行(从 1 开始)有i个元素 - 每行的第一个和最后一个元素都是 1
- 中间的元素:
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]
ArrayList 方法:
- 创建一个
ArrayList存储所有行(每行也是一个ArrayList) - 生成第一行:
[1] - 从第二行开始,利用上一行生成当前行
- 返回结果
代码实现
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 的性能?
答案:
- 指定初始容量:如果知道大概大小,使用
new ArrayList<>(initialCapacity) - 避免频繁扩容:预估容量,减少扩容次数
- 尾部操作:尽量在尾部添加/删除元素
- 批量操作:使用
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); // 不需要扩容
}
学习建议
- 理解原理:掌握 ArrayList 的底层实现(数组 + 扩容)
- 熟练使用:多练习 ArrayList 的常用方法
- 性能优化:了解时间复杂度,选择合适的操作
- 实际应用:结合 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 数据结构与算法
浙公网安备 33010602011771号