数组
循环中的++i和i++结果一样的原因,但是性能不一样,在大量数据的时候++i的性能要比i++的性能好原因: i++由于是在使用当前值之后再+1,所以需要一个临时的变量来转存。 而++i则是在直接+1,省去了对内存的操作的环节,相对而言能够提高性能
三数之和
leetcode题号:15
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组
排序、双指针
a下标从0循环到n,b和c存在关系,可用双指针
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}
在排序数组中查找元素的第一个和最后一个位置
leetcode题号:34
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]
设计并实现时间复杂度为 O(log n)
的算法解决此问题
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
class Solution {
/**
* 二分查找
*/
public int[] searchRange(int[] nums, int target) {
//寻找左边界(这里寻找第一个 >= target的索引)
int leftIndex = search(nums, target);
if (leftIndex >= nums.length || nums[leftIndex] != target){
return new int[]{-1, -1};
}
//寻找右边界(这里寻找第一个 >= target+1的索引)
int rightIndex = search(nums, target + 1);
return new int[]{leftIndex, rightIndex - 1};
}
/**
* 寻找第一个>=目标值的索引, 找不到则返回数组长度
*/
private int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right){
int mid = (right - left) / 2 + left;
if (nums[mid] >= target){
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}
跳跃游戏 II
leetcode题号:45
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2
思路: 具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1
class Solution {
public int jump(int[] nums) {
int length = nums.length;
int end = 0;
int maxPosition = 0;
int steps = 0;
for (int i = 0; i < length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;
}
}
全排列
leetcode题号:46
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
回溯法:一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。
回溯 + 使用一个 boolean 数组记录 nums[i] 是否被使用,用时击败 100%
class Solution {
private List<List<Integer>> lists = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
List<Integer> list = new ArrayList<>(n);
boolean[] used = new boolean[n];
int count = 0;
backtrace(list, used, count, nums);
return lists;
}
public void backtrace(List<Integer> list, boolean[] used, int count, int[] nums) {
if (count == nums.length) {
lists.add(new ArrayList(list));
return;
}
for (int i = 0; i < used.length; i++) {
if (!used[i]) {
list.add(nums[i]);
used[i] = true;
backtrace(list, used, count + 1, nums);
list.remove(list.size() - 1);
used[i] = false;
}
}
}
}
打家劫舍
leetcode题号:198
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路:动态规划
偷窃第 k 间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int[] dp = new int[length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[length - 1];
}
}
目标和
leetcode题号:494
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
使用回溯的方法遍历所有的表达式,回溯过程中维护一个计数器 \textit{count}count,当遇到一种表达式的结果等于目标数 \textit{target}target 时,将 \textit{count}count 的值加 11。遍历完所有的表达式之后,即可得到结果等于目标数 \textit{target}target 的表达式的数目。
class Solution {
int count = 0;
public int findTargetSumWays(int[] nums, int target) {
backtrack(nums, target, 0, 0);
return count;
}
public void backtrack(int[] nums, int target, int index, int sum) {
if (index == nums.length) {
if (sum == target) {
count++;
}
} else {
backtrack(nums, target, index + 1, sum + nums[index]);
backtrack(nums, target, index + 1, sum - nums[index]);
}
}
}
零钱兑换 II
leetcode题号:518
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
动态规划
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
}