🔥 LeetCode 热题 HOT 100(91-100)
461. 汉明距离
思路:统计 x
和 y
异或得到的数中 1 的个数。异或时比特位相同得 0,否者得 1。
class Solution {
public int hammingDistance(int x, int y) {
int res = x ^ y;
return countbits(res);
}
//统计比特位为1的个数(338. 比特位计数)
private int countbits(int i) {
int cnt = 0;
while (i != 0) {
i = i & (i - 1);
cnt++;
}
return cnt;
}
}
494. 目标和
联系:416. 分割等和子集
思路一:dfs
class Solution {
public int findTargetSumWays(int[] nums, int target) {
return dfs(nums, 0, 0, target);
}
private int dfs(int[] nums, int index, int sum, int target) {
// base case
if (nums.length == index) {
if (sum == target) {
return 1;
} else {
return 0;
}
}
//对于任何一个元素可以选择 + 或 -
return dfs(nums, index + 1, sum + nums[index], target) +
dfs(nums, index + 1, sum - nums[index], target);
}
}
思路二:dfs + 备忘录
class Solution {
public int findTargetSumWays(int[] nums, int target) {
return dfs(nums, 0, 0, target);
}
// 备忘录:也可用一个二维数组,一维表示元素和sum,一维表示当前索引index
private Map<String, Integer> map = new HashMap<>();
private int dfs(int[] nums, int index, int sum, int target) {
if (nums.length == index) {
if (sum == target) {
return 1;
} else {
return 0;
}
}
// 描述一个子问题的两个变量是 sum 和 index,组成 key 字符串
String key = sum + "&" + index;
if (map.containsKey(key)) {
return map.get(key);
}
int ret = dfs(nums, index + 1, sum + nums[index], target) +
dfs(nums, index + 1, sum - nums[index], target);
map.put(key, ret);
return ret;
}
}
思路三:记数组的元素和为sum
,添加正号的元素和为pos
,则其余添加负号的元素之和为sum − pos
,于是有:
pos - (sum - pos) = target
,即pos = (sum + target) / 2
。因此问题等价于数组中选取若干元素使得和为pos
的方案数,转化为01背包问题,使用动态规划。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i : nums) {
sum += i;
}
if (Math.abs(target) > sum || (sum + target) % 2 == 1) {
return 0;
}
int bag = (sum + target) / 2;
int len = nums.length;
// dp[i][j] 表示背包容量为 i,有前 j 个物品时填满背包的方法数
int[][] dp = new int[bag + 1][len + 1];
// base case:
// dp[0][0] = 1; 背包容量为 0 且 没有物品可选可认为不选即满,固有一种方式填满
// dp[i][0] = 0, i >= 1; 背包容量 大于等于1 且没有物品可选则无法填满
dp[0][0] = 1;
for (int i = 0; i < bag + 1; i++) {
for (int j = 1; j < len + 1; j++) {
// 如果不能装下前j个物品中的最后一个
if (i < nums[j - 1]) {
dp[i][j] = dp[i][j - 1];
// 能装下,可以选择装或者不装
} else if (i >= nums[j - 1]) {
dp[i][j] = dp[i][j - 1] + dp[i - nums[j - 1]][j - 1];
}
}
}
return dp[bag][len];
}
}
538. 把二叉搜索树转换为累加树
思路:根据搜索二叉树前序遍历有序性改造,使用 右 根 左
的顺序遍历二叉树(由大到小)并记录 累加和 以更新结点值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
// 按 右 根 左 的顺序累加和(由大到小遍历)
private int sum = 0;
private void dfs(TreeNode node) {
if (node == null) {
return;
}
dfs(node.right);
sum += node.val;
node.val = sum;
dfs(node.left);
}
}
543. 二叉树的直径
思路:对于任何一个结点,肯定要先知道左右子树的情况才能知道以自己为根的二叉树的直径。所以改造后序遍历,类似124. 二叉树中的最大路径和
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return maxDiameter;
}
private int maxDiameter = 0;
private int dfs(TreeNode node) {
if (node == null) {
return 0;
}
int left = dfs(node.left);
int right = dfs(node.right);
maxDiameter = Math.max(maxDiameter, left + right);
return Math.max(left, right) + 1;
}
}
560. 和为K的子数组
思路一:暴力遍历所有区间并判断
class Solution {
public int subarraySum(int[] nums, int k) {
int len = nums.length;
int cnt = 0;
// i 为区间左边界,j 为右边界
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
int sum = 0;
for (int n = i; n <= j; n++) {
sum += nums[n];
}
if (sum == k) {
cnt++;
}
}
}
return cnt;
}
}
时间复杂度:O(n3),运行超时。
思路二:代码一中固定左边界i
,依次增大右边界j
的结果可以复用。
class Solution {
public int subarraySum(int[] nums, int k) {
int len = nums.length;
int cnt = 0;
for (int i = 0; i < len; i++) {
int sum = 0;
for (int j = i; j < len; j++) {
sum += nums[j];
if (sum == k) {
cnt++;
}
}
}
return cnt;
}
}
时间复杂度:O(n2)
思路三: 前缀和,一个元素的前缀和,表示数组当前元素到第一个元素所有元素之和。就数组来说,位于后面的元素的前缀和减去前面的元素的前缀和只差表示两个元素之间(左开右闭)的元素和。
备注:前缀和应注意:在树中,只有子结点减去自己的父辈、祖先结点的前缀和才有意义;在数组中,只有于后面的元素减去前面的元素的前缀和才有意义。
class Solution {
public int subarraySum(int[] nums, int k) {
int len = nums.length;
//key : 前缀和;value : 前缀和次数
Map<Integer, Integer> map = new HashMap<>();
//前缀和为 0 的元素有 1 个
map.put(0, 1);
int cnt = 0;
int prefixSum = 0;
for (int i = 0; i < len; i++) {
prefixSum += nums[i];
// 当前元素前缀和 与其之前元素前缀和只差为 k的次数
if (map.containsKey(prefixSum - k)) {
cnt += map.get(prefixSum - k);
}
map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1);
}
return cnt;
}
}
时间复杂度:O(n)
581. 最短无序连续子数组
思路一:拷贝一个新数组,对新数组排序后依次比较各个元素。
class Solution {
public int findUnsortedSubarray(int[] nums) {
int len = nums.length;
int newNums[] = new int[len];
// 拷贝一个新数组
System.arraycopy(nums, 0, newNums, 0, len);
// 对新数组排序
Arrays.sort(newNums);
// 从左依次对比两个数组,左起第一个不同元素的位置即为待查找区间左边界(包含)
int left = 0;
while (left < len && nums[left] == newNums[left]) {
left++;
}
// 原数组有序
if (left == len) {
return 0;
}
// 从右依次对比两个数组,右起第一个不同元素的位置即为待查找区间右边界(包含)
int right = len - 1;
while (right >= 0 && nums[right] == newNums[right]) {
right--;
}
return right - left + 1;
}
}
复杂度取决于排序算法:O(nlogn)
思路二:见代码
class Solution {
public int findUnsortedSubarray(int[] nums) {
int len = nums.length;
int left = 0, right = 0;
//从左向右遍历,如果当前元素大于等于最大值则更新最大值,
//小于则说明存在逆序,当前元素一定是待排序区间的元素,更新区间右边界
int max = Integer.MIN_VALUE;
for (int i = 0; i < len; i++) {
if (nums[i] >= max) {
max = nums[i];
} else if (nums[i] < max) {
right = i;
}
}
//数组有序
if (right == 0) {
return 0;
}
//从右向左遍历,如果当前元素小于等于最小值则更新最小值,
//大于则说明存在逆序,当前元素一定是待排序区间的元素,更新区间左边界
int min = Integer.MAX_VALUE;
for (int i = right; i >= 0; i--) {
if (nums[i] <= min) {
min = nums[i];
} else if (nums[i] > min) {
left = i;
}
}
return right - left + 1;
}
}
617. 合并二叉树
思路:和合并有序链表类似
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
return merge(root1, root2);
}
private TreeNode merge(TreeNode node1, TreeNode node2) {
//base case
if (node1 == null) {
return node2;
}
if (node2 == null) {
return node1;
}
TreeNode newNode = new TreeNode(node1.val + node2.val);
newNode.left = merge(node1.left, node2.left);
newNode.right = merge(node1.right, node2.right);
return newNode;
}
}
621. 任务调度器
推荐题解:简明易懂的Java解答
class Solution {
public int leastInterval(char[] tasks, int n) {
int[] counts = new int[26];
//记录每个任务(字母)出现需要执行的次数
// 'A' 放在 counts[0], 'B'、'C'依次向后
for (char ch : tasks) {
counts[ch - 'A']++;
}
Arrays.sort(counts);
// [A, A, B, B, C]
// 需要执行最多次数的任务的执行次数, A和B都是2次
int maxTimes = counts[25];
// 执行最多次数的不同任务个数,2(A和B)
int maxCount = 1;
for (int i = 25; i >= 1; i--) {
if (counts[i] == counts[i - 1]) {
maxCount++;
} else {
//排序后依次递减,无需继续判断
break;
}
}
int times = (maxTimes - 1) * (n + 1) + maxCount;
//如果冷却时间n少于任务种数,结果应该为tasks.length,这种情况下计算的times小于实际结果
return Math.max(tasks.length, times);
}
}
647. 回文子串
思路:动态规划
class Solution {
public int countSubstrings(String s) {
int len = s.length();
// dp[i][j] :下标 i, j 之间的子串是否为回文串
boolean[][] dp = new boolean[len][len];
int cnt = 0;
for (int j = 0; j < len; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(j) == s.charAt(i) && (j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
cnt++;
}
}
}
return cnt;
}
}
739. 每日温度
思路:单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len = temperatures.length;
Deque<Integer> stack = new LinkedList<>();
//初始全为0
int[] res = new int[len];
for (int i = 0; i < len; i++) {
int temp = temperatures[i];
while (!stack.isEmpty() && temp > temperatures[stack.peek()]) {
res[stack.peek()] = i - stack.pop();
}
stack.push(i);
}
return res;
}
}