LeetCode题号[200,299]刷题总结
201. 数字范围按位与
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
思路
- 找出m,n两个端点的共同公共前缀即可
public int rangeBitwiseAnd(int m, int n) {
int ans = 0;
int key = 1<<31;
while(key != 0){
if ((m & key) != 0 && (n & key) != 0){
ans += key;
} else if ((m & key) != 0 || (n & key) != 0){
break;
}
key >>>= 1;
}
return ans;
}
- 有种相对更简单的方法
- 每次将n的最右端的1变为0,直到\(n <= m\)为止,此时与运算结果就是共同公共前缀
public int rangeBitwiseAnd(int m, int n) {
while (m < n){
n &= n-1;
}
return m & n;
}
207. 课程表
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
思路
- 拓扑排序
class Solution {
private Map<Integer, List<Integer>> map = new HashMap<>();
private Queue<Integer> queue = new LinkedList<>();
private int[] inDegree ;
public boolean canFinish(int numCourses, int[][] prerequisites) {
map.clear();
queue.clear();
inDegree = new int[numCourses];
for (int i = 0;i < numCourses;i++){
map.put(i,new ArrayList<>());
inDegree[i] = 0;
}
for (int i = 0;i < prerequisites.length;i++){
map.get(prerequisites[i][1]).add(prerequisites[i][0]);
inDegree[prerequisites[i][0]]++;
}
for (int i = 0;i < numCourses;i++){
if (inDegree[i] == 0){
queue.offer(i);
}
}
while (!queue.isEmpty()){
List<Integer> list = map.get(queue.poll());
for (Integer i : list){
inDegree[i]--;
if (inDegree[i] == 0){
queue.offer(i);
}
}
}
for (int i = 0;i < numCourses;i++){
if (inDegree[i] != 0){
return false;
}
}
return true;
}
}
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
思路
- 二分+后缀和
- 时间复杂度\(O(nlogn)\)
- 遍历整个数组,当前位置为最右端,二分后缀和
class Solution {
public int binarySearch(int s,int left,int right,int sum[]){
while (left + 1 < right){
int mid = (left+right) >> 1;
if (sum[mid] < s){
right = mid;
}
else{
left = mid;
}
}
return sum[left] >= s ? left : -sum.length;
}
public int minSubArrayLen(int s, int[] nums) {
if (nums.length == 0){
return 0;
}
int ans = Integer.MAX_VALUE;
int[] sum = new int[nums.length];
sum[nums.length - 1] = nums[nums.length - 1];
for (int i = nums.length - 2;i >= 0;i--){
sum[i] = sum[i+1] + nums[i];
}
for (int i = 0;i < nums.length;i++){
ans = Integer.min(ans,i -
binarySearch(s + (i < nums.length - 1 ? sum[i+1] : 0),0,i+1,sum) + 1);
}
return ans > sum.length ? 0 : ans ;
}
}
- 双指针
- 涉及连续子数组这种的题目,一般都能用双指针解决,时间复杂度和空间复杂度通常也是最优的
- 时间复杂度\(O(n)\)
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int left = 0,right = 0,sum = 0,ans = Integer.MAX_VALUE;
while (left <= right && right < nums.length){
sum += nums[right++];
while (sum - nums[left] >= s && left < right){
sum -= nums[left++];
}
if (sum >= s){
ans = Integer.min(ans,right - left);
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
210. 课程表 II
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
思路
- 跟207题基本一致,等同于将其队列中的数值按照顺序放入数组即可
class Solution {
private Map<Integer, List<Integer>> map = new HashMap<>();
private Queue<Integer> queue = new LinkedList<>();
private int[] inDegree ;
public int[] findOrder(int numCourses, int[][] prerequisites) {
map.clear();
queue.clear();
inDegree = new int[numCourses];
int[] ans = new int[numCourses];
int cnt = 0;
for (int i = 0;i < numCourses;i++){
map.put(i,new ArrayList<>());
inDegree[i] = 0;
}
for (int i = 0;i < prerequisites.length;i++){
map.get(prerequisites[i][1]).add(prerequisites[i][0]);
inDegree[prerequisites[i][0]]++;
}
for (int i = 0;i < numCourses;i++){
if (inDegree[i] == 0){
queue.offer(i);
ans[cnt++] = i;
}
}
while (!queue.isEmpty()){
List<Integer> list = map.get(queue.poll());
for (Integer i : list){
inDegree[i]--;
if (inDegree[i] == 0){
queue.offer(i);
ans[cnt++] = i;
}
}
}
return cnt == numCourses ? ans : new int[0];
}
}
214. 最短回文串(Hard)
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
思路
- KMP
- 由于要求只能在最前面添加字符,所以就先找出原始字符串的前面的最长回文串,再将后面的那段逆转后放到最前面即可
- 例如,"aacecaaa"的前面最长回文串为"aacecaa",那么我们只需要将最后面的"a"逆转后反到最前面即可
- 如果用暴力法,那么,时间复杂度为\(O(n^{2})\)
- 但是,如果采用KMP,可以使得时间复杂度降为\(O(n)\)
- KMP就是求最长的相同前后缀,那么如何转换成KMP?
- 构建一个新的字符串,即s + "#" + s.reverse()
- 例如,"aacecaaa#aaacecaa"
- 那么,他的最长公共前后缀就是"aacecaa"
class Solution {
public String shortestPalindrome(String s) {
String str = s + "#" + new StringBuilder(s).reverse();
int[] next = new int[str.length()];
int maxLength = 0;
next[0] = 0;
for (int i = 1;i < str.length();i++){
while (maxLength > 0 && str.charAt(i) != str.charAt(maxLength) ){
maxLength = next[maxLength - 1];
}
if (str.charAt(maxLength) == str.charAt(i)){
maxLength++;
}
next[i] = maxLength;
}
return new StringBuilder(s.substring(next[str.length()-1])).reverse() + s;
}
}
215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
思路
- 最小堆
- PriorityQueue默认为最小堆
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
int ans = 0;
for (int num : nums){
if (queue.size() < k) {
queue.add(num);
} else if (queue.peek() < num){
queue.poll();
queue.add(num);
}
}
return queue.peek();
}
}
229. 求众数 II
给定一个大小为 n 的数组,找出其中所有出现超过
⌊ n/3 ⌋
次的元素。说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。
思路
- 摩尔投票法
public List<Integer> majorityElement(int[] nums) {
List<Integer> ans = new ArrayList<>();
int num1,cnt1,num2,cnt2;
num1 = cnt1 = num2 = cnt2 = 0;
for (int num : nums) {
if (num == num1 ) {
cnt1++;
continue;
}
if (num == num2) {
cnt2++;
continue;
}
if (cnt1 == 0) {
num1 = num;
cnt1++;
continue;
}
if (cnt2 == 0) {
num2 = num;
cnt2++;
continue;
}
cnt1--;
cnt2--;
}
cnt1 = cnt2 = 0;
for (int num : nums ){
if (num1 == num) {
cnt1++;
} else if (num2 == num) {
cnt2++;
}
}
if (cnt1 * 3 > nums.length) {
ans.add(num1);
}
if (cnt2 * 3 > nums.length) {
ans.add(num2);
}
return ans;
}
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
思路
- 递归
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 遇到目标值时直接返回
if (root == null || root.val == p.val || root.val == q.val) {
return root;
}
// 搜索左右子树
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
// 左右子树都能找到目标值,此时节点为最近公共祖先
if (left != null && right != null) {
return root;
}
// 左子树为空,说明目标值都在右子树,
return left == null ? right : left;
}
}
260. 只出现一次的数字 III
给定一个整数数组
nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
思路
- 假设出现次数为1的数为x,y
- 任意两个相同数异或,其值为0,所以可以先异或所有数,排除其余出现两次的数,此时异或结果为x,y的异或值
- 我们取这个异或值的任意一个为1的二进制位,此时x,y &这个二进制位,必定有一个值为0,所以就能排除掉x,y其中的一个数了。
- 之后再次进行异或计算,由于排除掉一个x或y,而其他数都出现两次,所以最终值必定是x,y的其中一个。
class Solution {
public int[] singleNumber(int[] nums) {
int or = 0;
for (int num : nums) {
or ^= num;
}
int dif = or & (-or);
int x = 0;
for (int num : nums) {
if ((num & dif) != 0) {
x ^= num;
}
}
return new int[]{x,x^or};
}
}
264. 丑数 II
编写一个程序,找出第
n
个丑数。丑数就是质因数只包含
2, 3, 5
的正整数
思路
- dp三指针
- 任意一个丑数(除了1)都是前面一个丑数*2或*3或*5获得
- 采用三指针,下一个丑数就是当前指针所在位置的数分别乘以2,3,5取得的最小值
class Solution {
private int dp[] = null;
Solution(){
int i2,i3,i5;
i2 = i3 = i5 = 0;
dp = new int[1700];
dp[0] = 1;
for (int i = 1;i <= 1690;i++) {
int value = Integer.min(dp[i2]*2,Integer.min(dp[i3]*3,dp[i5]*5));
dp[i] = value;
if (dp[i2]*2 == value) i2++;
if (dp[i3]*3 == value) i3++;
if (dp[i5]*5 == value) i5++;
}
}
public int nthUglyNumber(int n) {
return dp[n-1];
}
}
279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如
1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
思路
- dp
class Solution {
private int[] dp = null;
public int numSquares(int n) {
dp = new int[n+1];
dp[0] = 0;
for (int i = 1;i <= n;i++){
dp[i] = Integer.MAX_VALUE;
for (int j = 1;j*j <= i;j++) {
dp[i] = Integer.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
- 数学法降维打击,代码略