🔥 LeetCode 热题 HOT 100(1-10)
1. 两数之和
思路一:暴力遍历所有组合
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[] {i, j};
}
}
}
//没找到
return new int[] {-1, -1};
}
}
思路二:利用map,key存储元素凑成target所需的差值,value存储元素下标
class Solution {
public int[] twoSum(int[] nums, int target) {
//value代表下标,key代表target与当前元素只差,即:target - nums[value]
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
return new int[] {map.get(nums[i]), i};
} else {
map.put(target - nums[i], i);
}
}
//没找到
return new int[] {-1, -1};
}
}
2. 两数相加
思路:直接模拟加法
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode cur1 = l1;
ListNode cur2 = l2;
//哑结点
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
//进位
int carry = 0;
while (cur1 != null || cur2 != null || carry != 0) {
int val1 = cur1 != null ? cur1.val : 0;
int val2 = cur2 != null ? cur2.val : 0;
int sum = val1 + val2 + carry;
int reminder = sum % 10;
carry = sum / 10;
//尾插法
ListNode tempNode = new ListNode(reminder);
cur.next = tempNode;
cur = cur.next;;
if (cur1 != null) cur1 = cur1.next;
if (cur2 != null) cur2 = cur2.next;
}
return dummy.next;
}
}
3. 无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
//窗口左右边界,左闭右开
int left = 0, right = 0;
//存储字符及其在窗口中的个数
Map<Character, Integer> window = new HashMap<>();
int maxLen = 0;
while (right < s.length()) {
//窗口扩大
char rightChar = s.charAt(right);
right++;
window.put(rightChar, window.getOrDefault(rightChar, 0) + 1);
//如果新加入窗口的字符个数大于1次,窗口应该收缩
while(window.get(rightChar) > 1) {
char leftChar = s.charAt(left);
left++;
window.put(leftChar, window.get(leftChar) - 1);
}
maxLen = Math.max(maxLen, right - left);
}
return maxLen;
}
}
4. 寻找两个正序数组的中位数
思路一:直接合并两个有序数组,根据数组长度求中位数。时间复杂为O(m+n)
,空间复杂度为O(m+n)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int[] tempArr = new int[m + n];
int i = 0, j = 0;
int k = 0;
while(i < m && j < n) {
if (nums1[i] < nums2[j]) {
tempArr[k++] = nums1[i++];
} else {
tempArr[k++] = nums2[j++];
}
}
//nums1还有剩余
while (i < m) {
tempArr[k++] = nums1[i++];
}
//nums2还有剩余
while (j < n) {
tempArr[k++] = nums2[j++];
}
int midIndex = (m + n) / 2;
double mid = 0;
//长度为偶数
if ((m + n) % 2 == 0) {
mid = (tempArr[midIndex - 1] + tempArr[midIndex]) / 2.0;
} else {
mid = tempArr[midIndex];
}
return mid;
}
}
思路二:无需真正合并数组,先根据两个数组的长度确定中位数是合并后数组中的第几个数,然后根据规则将指向两个数组的下标移动对应次数就好。时间复杂为O(m+n)
,空间复杂度为O(1)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
//i, j分别表示nums1和nums2的分割线,i为[0, m],j为[0, n];
//[0, i)表示nums1中已经遍历过的下标,对应j类似
int i = 0, j = 0;
//记录最新遍历的两个数
int newest = 0, secondNew = 0;
//奇数:中位数是第(m+n)/2 + 1个数(下标从1开始)
//偶数:中位数是第(m+n)/2和第(m+n)/2 + 1数的平均数(下标从1开始)
int cnt = (m + n) / 2 + 1;
for (int k = 0; k < cnt; k++) {
secondNew = newest;
// j >= n 表示指向nums2已经没有元素
if (j >= n || (i < m && nums1[i] < nums2[j])) {
newest = nums1[i];
i++;
} else {
newest = nums2[j];
j++;
}
}
//偶数
if ((m + n) % 2 == 0) {
return (newest + secondNew) / 2.0;
} else {
return newest;
}
}
}
终于说服自己
思路三:使用二分法直接在两个数组中找中位数分割线,使得nums1
和nums2
中分割线满足以下性质即可根据分割线左右的数来确定中位数:
前置:m = nums1.length
,n = nums2.length
。设i
为nums1
中分割线,则取值为[0, m]
,表示分割线左侧元素下标为[0, i-1]
,分割线右侧元素下标为[i, m-1]
;设j
为nums2
中分割线,....。
m+n
为偶数:i + j = (m + n )/2
,为奇数:i + j = (m + n)/2 + 1
。分割线左侧元素
小于等于
分割线右侧元素。由于两个数组均为正序数组,则只需要要求:nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
;由于该条件等价于在[0, m]
中找到最大的i
使得nums1[i-1] <= nums2[j]
,因此可以使用二分查找。(证明:假设我们已经找到了满足条件的最大i
,使得nums1[i-1] <= nums2[j]
,那么此时必有nums[i] > nums2[j]
,进而有nums[i] > nums2[j-1]
)
分割线找到后,若m+n
为奇数,分割线左侧的最大值即为中位数;若为偶数,分割线左侧的最大值与分割线右侧的最小值的平均数即为中位数。时间复杂度:O(log(min(m, n)))
,空间复杂度:O(1)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 始终保证nums1为较短的数组,nums1过长可能导致j为负数而越界
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m = nums1.length;
int n = nums2.length;
// m+n 为奇数,分割线要求左侧有 (m+n)/2 + 1 个元素
// m+n 为偶数,分割线要求左侧有 (m+n)/2 个元素
// 两种情况其实可以统一写作 (m+n+1)/2,表示对(m+n)/2向上取整
// 对整数来说,向上取整等于:(被除数 + (除数 - 1)) / 除数
// 也可以使用Math类中提供的库函数
int leftTotal = (m + n + 1) / 2;
int left = 0, right = m;
while (left < right) {
// +1 向上取整避免 left + 1 = right 时可能无法继续缩小区间而陷入死循环
int i = left + (right - left + 1) / 2;
int j = leftTotal - i;
//要找最大i,使得nums1[i-1] <= nums2[j]
//使用对立面缩小区间
if (nums1[i - 1] > nums2[j]) {
// [i+1, m]均不满足
right = i - 1;
} else {
// i满足说明[0, i-1]均不为满足条件的最大i,舍去以缩小区间
left = i;
}
}
//退出循环时left=right,表示最终nums1中分割线的位置
int i = left;
//nums2中分割线的位置
int j = leftTotal - left;
System.out.println(i);
//判断极端情况
int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1]; //nums1分割线左边没有元素
int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1]; //nums2分割线左边没有元素
int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i]; //nums1分割线右边没有元素
int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j]; //nums2分割线右边没有元素
if ((m + n) % 2 == 0) {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
} else {
return Math.max(nums1LeftMax, nums2LeftMax);
}
}
}
备注:这里使用的二分法和二分法查找x的平方根使用的方法很像,都是要查找满足条件的最大值。
参考:官方题解:方法二、wei哥:二分查找定位短数组的「分割线」(Java )
5. 最长回文子串
思路:动态规划获取任意两个区间的子串是否为回文子串,如果是则记录开始下标和长度
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
int maxLen = 0;
int start = 0;
//dp[i][j]: 字符串s[i, j]是否为回文字符串
boolean[][] dp = new boolean[len][len];
for (int j = 0; j < len; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
if (j - i + 1 > maxLen) {
maxLen = j - i + 1;
start = i;
}
}
}
}
return s.substring(start, start + maxLen);
}
}
10. 正则表达式匹配
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length();
int pLen = p.length();
//状态:dp[i][j] 表示s的前i个字符和p的前j个字符能否匹配,即 s[0, i-1] 和 p[0, j-1] 能否匹配
boolean[][] dp = new boolean[sLen + 1][pLen + 1];
//前置:i, j分别代表dp的横、纵下标,对应的s、p的下标都应减去1
//初始值:dp 默认都为flase
//dp[0][0] = true, 即s和p都为空
//dp[i][0] = false, 其中i >= 1, 即s不为空p为空
//dp[0][1] = false, 由于p[0]不能为*,s为空,p只有一个字符且不为'*'的情况下必然不能匹配成功
//s为空,p不为空且p[0, j-1]以'*'结尾时,还不能直接断定dp的值。
//因为'*'可以选择将它前面的字符匹配零次以消除'*'前面的字符。
//因此 dp[0][j] = dp[0][j - 2], j >= 2。若s为空,p不为空且 p[0, j-1] 不以'*'结尾,那么有:
//dp[0][j] = false, j >= 1。
dp[0][0] = true;
for (int j = 2; j < pLen + 1; j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
}
//状态转移:
//当 s[0, i-1] 和 p[0, j-1] 的末尾字符相等或p的末尾字符为'.',则有:dp[i][j] = dp[i - 1][j - 1];
//当 p[0, j-1] 的末尾字符即 p[j - 1] 为'*'需要讨论:
// 若 s[i - 1] 与 p[j - 2] 相等,如:s(ab), p(cab*),那么*可以选择将b 重复一次 则s末尾的b和p末尾的b*匹配抵消,
// 则有:dp[i][j] = dp[i - 1][j - 2];
// 同时*也可以选择将b 重复多次 以匹配抵消s中最后一个b,此时p虽然也损失一个b但任然还剩多个b,可以将其看成b*
// 故有:dp[i][j] = dp[i - 1][j];
// 此外*也可以选择将b 去掉,故:dp[i][j] = dp[i][j - 2]
// 综上:dp[i - j] = dp[i - 1][j - 2] || dp[i - 1]dp[j] || dp[i][j - 2]
// 若 p[j - 2] 为 '.',同上
// 若 s[i - 1] 与 p[j - 2] 不等且后者不为'.',如:s(ab), p(eabd*),那么*可以选择将d 去掉 ,
// 则有:dp[i][j] = dp[i][j - 2]
for (int j = 1; j < pLen + 1; j++) {
for (int i = 1; i < sLen + 1; i++) {
if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
if (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.') {
dp[i][j] = dp[i - 1][j -2] || dp[i - 1][j] || dp[i][j - 2];
} else {
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[sLen][pLen];
}
}
11. 盛最多水的容器
class Solution {
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int maxArea = 0;
while (left < right) {
int width = right - left;
int high = Math.min(height[left], height[right]);
int area = width * high;
maxArea = Math.max(area, maxArea);
//左边小则直接去掉,因为它和右边剩余的任意一个元素组成的面积都不会比当前更大
if (height[left] < height[right]) {
left++;
//去掉右边
} else {
right--;
}
}
return maxArea;
}
}
推荐题解:O(n) 双指针解法:理解正确性、图解原理(C++/Java)
15. 三数之和
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new LinkedList<>();
if (nums == null || nums.length < 3) {
return res;
}
//排序很重要
Arrays.sort(nums);
int len = nums.length;
for (int i = 0; i < len - 2; i++) { //最后两个数不用判断
//三数之和一定大于0,后序必然不存在为0的组合
if (nums[i] > 0) break;
//去重
if (i > 0 && nums[i] == nums[i - 1]) continue;
// left right 表示了 i 的右侧闭合区间
int left = i + 1;
int right = len - 1;
while (left < right) {
int target = nums[i] + nums[left] + nums[right];
if (target == 0) {
res.add(Arrays.asList(new Integer[] {nums[i], nums[left], nums[right]}));
//去重
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (target < 0) {
left++;
} else if (target > 0) {
right--;
}
}
}
return res;
}
}
推荐题解:画解算法:15. 三数之和
17. 电话号码的字母组合
回溯法即可
class Solution {
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() < 1) {
return res;
}
Map<Character, Character[]> map = new HashMap<>() {
{
put('2', new Character[] {'a', 'b', 'c'});
put('3', new Character[] {'d', 'e', 'f'});
put('4', new Character[] {'g', 'h', 'i'});
put('5', new Character[] {'j', 'k', 'l'});
put('6', new Character[] {'m', 'n', 'o'});
put('7', new Character[] {'p', 'q', 'r', 's'});
put('8', new Character[] {'t', 'u', 'v'});
put('9', new Character[] {'w', 'x', 'y', 'z'});
}
};
StringBuilder track = new StringBuilder();
dfs(map, digits, 0, track);
return res;
}
//存放最终的结果
private List<String> res = new LinkedList<>();
//回溯法获取所有结果
private void dfs(Map<Character, Character[]> map, String digits, int index, StringBuilder track) {
if (digits.length() == index) {
res.add(track.toString());
return;
}
Character[] charArr = map.get(digits.charAt(index));
for (int i = 0; i < charArr.length; i++) {
//选择
track.append(charArr[i]);
dfs(map, digits, index+1, track);
//撤销选择
track.deleteCharAt(index);
}
}
}
19. 删除链表的倒数第 N 个结点
思路:快慢指针都指向头,快指针先移动n,接着快慢指针一起向后移动,直至快指针到达末尾,此时根据满指针即可删除倒数第n个结点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//删除的可能是头结点,所以需要哑结点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode fast =dummyNode, slow = dummyNode;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
//删除结点
slow.next = slow.next.next;
return dummyNode.next;
}
}
🔥 LeetCode 热题 HOT 100(1-10)的更多相关文章
- LeetCode 热题 HOT 100(05,正则表达式匹配)
LeetCode 热题 HOT 100(05,正则表达式匹配) 不够优秀,发量尚多,千锤百炼,方可成佛. 算法的重要性不言而喻,无论你是研究者,还是最近比较火热的IT 打工人,都理应需要一定的算法能力 ...
- 🔥 LeetCode 热题 HOT 100(81-90)
337. 打家劫舍 III 思路:后序遍历 + 动态规划 推荐题解:树形 dp 入门问题(理解「无后效性」和「后序遍历」) /** * Definition for a binary tree nod ...
- 🔥 LeetCode 热题 HOT 100(71-80)
253. 会议室 II(NO) 279. 完全平方数 class Solution { public int numSquares(int n) { // dp[i] : 组成和为 i 的最少完全平方 ...
- 🔥 LeetCode 热题 HOT 100(51-60)
142. 环形链表 II 思路:快慢指针,快慢指针相遇后,慢指针回到头,快慢指针步伐一致一起移动,相遇点即为入环点 /** * Definition for singly-linked list. * ...
- 🔥 LeetCode 热题 HOT 100(31-40)
75. 颜色分类 思路:将 2 往后放,0 往前放,剩余的1自然就放好了. 使用双指针:left.right 分别指向待插入的 0 和 2 的位置,初始 left 指向数组头,right 指向数组尾部 ...
- 🔥 LeetCode 热题 HOT 100(21-30)
46. 全排列 思路:典型回溯法 class Solution { public List<List<Integer>> permute(int[] nums) { Linke ...
- 🔥 LeetCode 热题 HOT 100(61-70)
207. 课程表 思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能.因此可以将问题转换成: 判断有向图中是否存在环.使用 拓扑排序法 : 构建 入度表:记录每 ...
- 🔥 LeetCode 热题 HOT 100(41-50)
102. 二叉树的层序遍历 思路:使用队列. /** * Definition for a binary tree node. * public class TreeNode { * int val; ...
- 🔥 LeetCode 热题 HOT 100(11-20)
20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...
随机推荐
- 飞(fly)(数学推导,liu_runda的神题)
大概看了两三个小时的题解,思考量很大,实现简单........ 20分: 明显看出,每个点的贡献是x*(x-1)/2;即组合数C(x,2),从x个线段中选出2个的方案数,显然每次相交贡献为1,n^2枚 ...
- 剑指0ffer59.滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值. 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3输出: [3,3,5,5,6,7] ...
- 基于GIS的国土空间规划平台建设
本期介绍基于地理信息平台的国土空间规划平台的规划辅助编制应用.在梳理国土空间规划科学流程的基础上,将规划编制各关键环节信息化.工具化.智能化:充分发挥清华同衡大数据与智能模型相结合的定量评估.精准 ...
- windows 安装Git详解
windows 安装Git详解 一.Git简介 Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理. Git 是 Linus Torvalds 为了帮助管理 Lin ...
- MobileNet系列之MobileNet_v2
MobileNet系列之MobileNet_v1 Inception系列之Inception_v1 Inception系列之Batch Normalization Inception系列之Ince ...
- Spring Boot 无侵入式 实现RESTful API接口统一JSON格式返回
前言 现在我们做项目基本上中大型项目都是选择前后端分离,前后端分离已经成了一个趋势了,所以总这样·我们就要和前端约定统一的api 接口返回json 格式, 这样我们需要封装一个统一通用全局 模版api ...
- Redis 底层数据结构之整数集合
文章参考:<Redis 设计与实现>黄建宏 整数集合 整数集合时集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合数量不多时,就会使用整数集合 typedef struct i ...
- AcWing 1282. 搜索关键词
给定n个长度不超过50的由小写英文字母组成的单词,以及一篇长为m的文章. 请问,有多少个单词在文章中出现了. #include<bits/stdc++.h> using namespace ...
- Python装饰器-给你的咖啡加点料
今天你的咖啡加糖了吗? 让我们通过一个简单的例子来引出装饰器的概念及用法.在引出装饰器之前,我们先来了解一下函数的概念. 一.函数回顾 1.在python中函数是一等公民,函数也是对象.我们可以把函数 ...
- 南京大学计算机基础 ELF和可执行文件格式
1.可重定位目标文件格式 主要是由ELF头,一些节比如.text节,.rodata节,.data节,.bss节等,前面是只读的,后面是可读可写的,加上一个节头表 1.1 ELF头里面主要包含了16字节 ...