文章目录
三月刷题记录-part1
此博文为个人学习笔记,记录2021年3月Leetcode每日一题刷题记录
338. 比特位计数
题目链接
题目描述
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例1
输入: 2
输出: [0,1,1]
示例2
输入: 5
输出: [0,1,1,2,1,2]
进阶提示
- 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
- 要求算法的空间复杂度为O(n)。
- 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
分析
要在一次线性扫描的时间内解决问题,也就是说直接一次性往数组内遍历赋值。
所以需要找寻赋值规律。
1. 动态规划-低位有效判断
我们先来看这样一个例子
1111,1110他们的bits取决于
111的bits + 末位是否为1
1101,1100他们的bits取决于
110的bits + 末位是否为1
很容易可以得到递推公式:
bits[i] = bits[i >> 1] + (i & 1)
公式解释:
- bits[i] 表示第i个数的bit为1的个数
- i >> 1,表示i右移1位 可以把1110,1111变为 111,简单理解就是舍掉最低1位
- i & 1,与运算。将第i个数与1作与运算,如果末位为1,则i & 1的结果就是1,否则0。
代码
func countBits(num int) []int {
bits := make([]int, num+1)
for i := 1; i <= num; i++ {
bits[i] = bits[i>>1] + (i & 1)
}
return bits
}
2. 动态规划-设置最低有效位
1 & 0 = 001 & 000 = 0 = 000
2 & 1 = 010 & 001 = 0 = 000
3 & 2 = 011 & 010 = 2 = 010
4 & 3 = 100 & 011 = 0 = 000
5 & 4 = 101 & 100 = 4 = 100
6 & 5 = 110 & 101 = 4 = 100
7 & 6 = 111 & 110 = 6 = 110
8 & 7 = 1000 & 0111 = 0 = 0000
不难看出一个规律
n & n-1 得到一个数,这个数就是将n 的最末位的1变成0的结果所以我们可以得到这样一个递推公式:
bits[i] = bits[i & [i-1]] + 1
公式解释:
- i & i-1 将i的最末位1 变成了0,得到数m
- 这个m一定在i之前,因为最末位1变成了0,之前的位不变
- bits[i] = bits[m] + 1,因为末位1变成0,第i个相比较第m个,i的个数少1
代码
func countBits(num int) []int {
bits := make([]int, num+1)
for i := 1; i <= num; i++ {
bits[i] = bits[i&(i-1)] + 1
}
return bits
}
896. 单调数列
题目描述
如果数组是单调递增或单调递减的,那么它是单调的。
如果对于所有 i <= j,A[i] <= A[j],那么数组 A 是单调递增的。 如果对于所有 i <= j,A[i]> = A[j],那么数组 A 是单调递减的。
当给定的数组 A 是单调数组时返回 true,否则返回 false。
示例
示例 1:
输入:[1,2,2,3]
输出:true
示例 2:
输入:[6,5,4,4]
输出:true
示例 3:
输入:[1,3,2]
输出:false
示例 4:
输入:[1,2,4,5]
输出:true
示例 5:
输入:[1,1,1]
输出:true
分析
简单题,循环暴力破解即可
- 循环遍历数组,用前一个和后一个做比较
- 在比较的时候,要注意第一次比较的结果
- 如果第一次比较是小于,那么后面不能出现大于
- 如果第一次比较是大于,那么后面不能出现小于
- 如果第一次比较是等于,那么继续比较下一个
- 时间复杂度O(n)
代码
func isMonotonic(A []int) bool {
flag := 0
// flag 初始等于0,表示还未存在大于小于关系
// flag 为1 表示递增;flag 为2,表示递减
var val1, val2 int
for index := 0; index < len(A)-1; index++ {
val1 = A[index] // 前一个变量
val2 = A[index+1] // 后一个变量
// 相等则继续判断,无论递增递减都满足
if val1 == val2 {
continue
}
// 小于的判定
if val1 < val2 {
// 如果flag == 2, 说明之前是递减序列,返回false
if flag == 2 {
return false
// 如果flag == 0,第一次出现小于,赋值1
} else if flag == 0 {
flag = 1
}
}
// 大于的判定
if val1 > val2 {
// 如果flag == 1,说明之前是递增序列,返回false
if flag == 1 {
return false
// 如果flag == 0,第一次出现大于,赋值2
} else if flag == 0 {
flag = 2
}
}
}
// 全部遍历结束,到这步说明都满足
return true
}
303. 区域和检索 - 数组不可变
题目描述
给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。
实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], … , nums[j]))
示例
输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5);