前言
所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习
461.汉明距离
题目:
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
解题思路:
【位运算+Brian Kernighan算法】根据位运算中异或的性质,两个数异或,二进制中相同位置不同则取1,相同则取0,题目要求两个数二进制不同的位置数其实也就是求两个数异或结果的二进制中1的个数;如果求某个数二进制中1的个数,可以根据Brian Kernighan算法:每次z 和 z-1 按位与即 z & (z - 1) 都会 去掉z最后面的一个1变为0,就这样我们不挺的去让z和z-1按位与,直到z变为0,经历了几次就说明有几个1
代码(python):
class Solution(object):
def hammingDistance(self, x, y):
"""
:type x: int
:type y: int
:rtype: int
"""
# 定义结果值
res = 0
# 两个值取异或 根据异或的性质,不同为1,相同为0,此时z的二进制中1的个数就是两个数不同的位数
z = x ^ y
# 根据 Brian Kernighan 算法求 z的二进制中1个个数
while z:
# 每个z 和 z-1 按位与 都会 去掉z最后面的一个1变为0 就这样知道所以1变为0即z变为0,
# 也就是说经历了几次运算,二进制中就有几个1
z = z & (z - 1)
res += 1
return res
代码(java):
class Solution {
public int hammingDistance(int x, int y) {
int result = 0;
String xStr = Integer.toBinaryString(x);
String yStr = Integer.toBinaryString(y);
int chaZhi = xStr.length() - yStr.length();
if (chaZhi > 0){
for (int i = 0; i < Math.abs(chaZhi); i ++){
yStr = "0" + yStr;
}
}else{
for (int i = 0; i < Math.abs(chaZhi); i ++){
xStr = "0" + xStr;
}
}
for(int i = 0; i < xStr.length(); i ++){
if(xStr.charAt(i) != yStr.charAt(i)){
result ++;
}
}
return result;
}
}
知识点:
- Brian Kernighan 算法内容:每次z 和 z-1 按位与即 z & (z - 1) 都会 去掉z最后面的一个1变为0;可以根据算法求z的二进制中1的个数
原题连接:汉明距离
494.目标和
题目:
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
解题思路:
【动态规划】比较容易想到的办法是回溯,但是我写完之后超时了,这里就不赘述;另外一种方法是背包解法,也就是动态化,为什么这么说呢?我们可以计算出数组的总和,从而得到与目标值的差值,如果能拼成target,这个差值一定是大于0的偶数,大于0的原因是每个元素都是非负数,如果全加在一起都不能拼成那就没办法了,偶数的原因是如果能拼成target,那差值部分一定可以一分为二,一半是用加号连在一起,一半是用减号连在一起,这才差值部分才能自我抵消,总和就变成了target,到这这道题就变成了求一个数组中选取部分元素能达到某个值的选法有多少,也就是典型的背包问题,大致思想就是建立二维数组,横坐标代表拼成的target,纵坐标代表截止到数组索引位置,初始值为一般为横坐标或纵坐标为0的情况,根据关系依次递推每个位置的值,关于背包问题的写法不再赘述,直接看代码(其实是自己搞的有点乱,不知道咋说ಥ_ಥ)
代码(python):
class Solution(object):
def findTargetSumWays(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
# 计算数组总和
sum_val = 0
for num in nums:
sum_val += num
# 计算目标和总和的差值
diff_val = sum_val - target
# 如果差值小于0(说明及时全部都是加号也达不到目标)
# 如果差值不是偶数(能拼成target的条件是 差值部分可以一分为二,一半是加号一半是减号,如果不能一分为二 则说明没办法拼成target)
if diff_val < 0 or diff_val % 2 == 1:
return 0
# 到此为止问题就变成了 找到部分数的总和为差值的一半 有多少种选法 背包问题
mid = diff_val / 2
n = len(nums)
# 定义二维数组,横坐标代表target 范围是0到差值的一半, 纵坐标代表 索引之前 范围是0到数组长度+1 (因为是之前 所以要+1)
# 每个位置的含义就是,截止到该索引之前 能拼成 target 的选法有几种
res = []
for i in range(0, n + 1):
res.append([0] * (mid + 1))
# 初始值,当目标是0 且在索引0之前只有一种选法
res[0][0] =1
# 从 索引1之前 递推到 长度+1之前
for i in range(1, n + 1):
# 从目标为0 递推到 差值一半
for j in range(0, mid + 1):
# 如果该索引之前对应的数组位置的值小于等于当前目标 说明可以被选取
# 此时该位置的选法就有两种情况,截止到该索引-1之前选该目标值的选法(不选当前值) + 截止到该索引-1之前选 目标-当前值 的选法(选当前值)
if nums[i - 1] <= j:
res[i][j] = res[i - 1][j] + res[i - 1][j - nums[i - 1]]
# 如果该索引之前对应的数组位置的值大于当前目标 说明不可以被选取
# 此时该位置的选法就有两种情况,截止到该索引-1之前选该目标值的选法(不选当前值)
else:
res[i][j] = res[i - 1][j]
#最后返回截止到数组最后一个位置选差值一半的选法就是表达式的数组
return res[n][mid]
代码(java):
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int len = nums.length;
ArrayList<HashMap> res = new ArrayList<>();
for (int i = 0; i < len; i++ ){
HashMap<Integer, Integer> item = new HashMap<>();
if (i == 0){
item.put(nums[i], item.getOrDefault(nums[i], 0) + 1);
item.put(0 - nums[i], item.getOrDefault(0 - nums[i], 0) + 1);
res.add(item);
continue;
}
HashMap<Integer, Integer> preItem = res.get(i - 1);
for (Integer key : preItem.keySet()){
int curKey = key + nums[i];
item.put(curKey, item.getOrDefault(curKey, 0) + preItem.get(key));
int curKey2 = key - nums[i];
item.put(curKey2, item.getOrDefault(curKey2, 0) + preItem.get(key));
}
res.add(item);
}
HashMap<Integer, Integer> last = res.get(len - 1);
if (last.keySet().contains(target)){
return last.get(target);
}else{
return 0;
}
}
}
知识点:
- 无
原题连接:目标和
538.把二叉搜索树转换为累加树
题目:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
解题思路:
【反向中序遍历】对于每个节点,要将该值改为原来的大于等于大的值的和,根据二叉搜索树的性质,右子树的节点都大于当前节点,对于左子数来说当前节点和兄弟节点都比他的值大,所以我们要计算累加和的关系,就应该是先处理右子树并且计算右子数的累加和,再更新当前节点的值,最后再根据已经得到的累加和处理左子树,这个顺序其实就是中序遍历的反序操作
代码(python):
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
#定义变量,用来存放截止到现在所累加的和
sum_val = 0
def convertBST(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
# 如果当前节点是空节点,直接返回
if not root:
return
# 反向中序遍历
# 递归先处理右节点 目的是计算出 右子树的sum_val
self.convertBST(root.right)
# 在处理当前节点:更新当前节点值 为:原值 + 右子树的累加和
root.val = root.val + self.sum_val
# 计算截止到当前累加和 用来左子数的跟新
self.sum_val = root.val
# 递归处理左节点
self.convertBST(root.left)
# 返回根节点即可
return root
代码(java):
/**
* 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 preSum = 0;
public TreeNode convertBST(TreeNode root) {
if(root != null){
convertBST(root.right);
root.val += preSum;
preSum = root.val;
convertBST(root.left);
return root;
}else{
return null;
}
}
}
知识点:
- 无
原题连接:把二叉搜索树转换为累加树
560.和为K的子数组
题目:
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
解题思路:
【前缀和】如果我们遍历数组以各个节点,以当前节点为数组结尾向前推进直到0索引,记录下这个过程中出现K的次数也就是结果,但是这样时间复杂度是On方;我们可以换个思路,如果说从索引0到当前位置的和(前缀和)是sum,从索引0到之前的某个位置子数组的和是sum - k,那从该位置到当前位置的子数组和不就是k么,有多少个这样的位置就有多少个和为K的子数组,而从索引0到某个位置的和可以在遍历数组中顺便记录下来,可以用一个字典,key是前缀和,val是个数,代表从0到某个位置的和为key的有val个,这样边遍历边更新这个字典,到后面位置时就可以查看是否有前缀和-k的数组了,从而就知道了是否有和为k的子数组;需要注意的是要有一个初始值,就是前缀和为0的子数组个数是1(为了从索引0到位置n,和恰巧为k的情况,那前缀和-k就是0,此时字典中又没有0,就会不会将此子数组记录)
代码(python):
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
# 定义结果值
res = 0
# 定义前缀和 代表截至到前一个位置 总和是多少
pre = 0
# 定义字典 用来存放 截止到当前位置和为key的子数组(该子数组一定是包括数组头位置)有val个
count = dict()
# 很重要 字典中的初始值 头位置时 和为0的子数组 有1个
count[0] = 1
# 遍历数组
for num in nums:
# 更新前缀和: 计算从头到当前位置的和
pre = pre + num
# 如果字典中包含 前缀和-k ,就说明有从头开始的子数组的和为前缀和-k,那从子数组结尾到当前位置和就是k
# 所以要更新结果值,有多少个和为前缀和-k的子数组 就有多少个和为k的子数组
if (pre - k) in count:
res = res + count[pre - k]
# 在字典中 将当前的前缀和的个数 + 1(说明多了一条从头开始到的子数组和为pre)
count[pre] = count.get(pre, 0) + 1
return res
代码(java):
class Solution {
public int subarraySum(int[] nums, int k) {
HashMap<Integer, Integer> res = new HashMap<>();
int sum = 0;
int count = 0;
res.put(0, 1);
for(int i = 0; i < nums.length; i++){
sum += nums[i];
if(res.containsKey(sum - k)){
count += res.get(sum -k);
}
res.put(sum, res.getOrDefault(sum, 0) + 1);
}
return count;
}
}
知识点:
- 无
原题连接:和为K的子数组
581.最短无序连续子数组
题目:
给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
请你找出符合题意的 最短 子数组,并输出它的长度。
示例 1:
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
示例 2:
输入:nums = [1,2,3,4]
输出:0
解题思路:
【遍历】很容易想到的方法是原列表复制一份后将新列表排序,然后从左往右找到第一个值不同的位置即为需要排序的左边界,从右往左找到第一个值不同的位置即有边界,长度就知道了,可是这样需要排序时间复杂度为Onlogn;另外一种方法是通过遍历的方式找到左右边界,主要思想是从左往右找到最后一个比左面的最大值小的位置就是右边界,从右往左找到最后要给比右面最小值大的位置就是左边界,左面的最大值和右面的最小值在遍历过程中要随时更新,另外通过索引i从左到右寻找,同时可以用len - i - 1的方式同步从右到左寻找,这样就可以将两次遍历变成了一次遍历,最后需要注意的是无论是哪种方法都要检验一下左右边界的合法性,如果还是初始定义的-1说明该数组本来就是有序的
代码(python):
class Solution(object):
def findUnsortedSubarray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 定义最大值 和 最小值
max_val = -sys.maxsize - 1
min_val = sys.maxsize
# 定义左右边界
left = -1
right = -1
n = len(nums)
# 遍历数组
for i in range(0, n):
# 从左到右找到 最后一个 比左面最大值 小的位置 就是右边界 这个过程中要记得更新 左面最大值
if nums[i] < max_val:
right = i
else:
max_val = nums[i]
# 从右到左找到 最后一个 比右面最小值 大的位置 就是左边界 这个过程中要记得更新 右面最小值
if nums[n - i - 1] > min_val:
# 注意这个是n - i - 1 而不是i 使用n -i -1的方式可以将两次遍历合成一次遍历
left = n - i - 1
else:
min_val = nums[n - i - 1]
# 最后要判断一下 left 或 right 是否更新 即是否找到这样的两个边界,如果没找到说明原数组本来就有序
if left == -1:
return 0
# 如果找到left 和right 返回长度即可
else:
return right - left + 1
代码(java):
class Solution {
public int findUnsortedSubarray(int[] nums) {
int[] newNums = new int[nums.length];
System.arraycopy(nums, 0, newNums, 0, nums.length);
Arrays.sort(newNums);
int left = -1;
int right = -1;
for(int i = 0; i < nums.length; i++){
if(newNums[i] != nums[i]){
left = i;
break;
}
}
for(int i = nums.length - 1; i >= 0; i--){
if(newNums[i] != nums[i]){
right = i;
break;
}
}
if (left == -1){
return 0;
}else{
return right - left + 1;
}
}
}
知识点:
- python中想要复制列表可以新建一个列表,将原列表当作参数传入:list(old_list);java中复制数组可以先新建一个等长的数组,然后用copy的方法:System.arraycopy(old_array, start, new_array, start, copy_length)
原题连接:最短无序连续子数组