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;
}
}
}

终于说服自己

思路三:使用二分法直接在两个数组中找中位数分割线,使得nums1nums2中分割线满足以下性质即可根据分割线左右的数来确定中位数:

前置:m = nums1.lengthn = nums2.length。设inums1中分割线,则取值为[0, m],表示分割线左侧元素下标为[0, i-1],分割线右侧元素下标为[i, m-1];设jnums2中分割线,....。

  • 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)的更多相关文章

  1. LeetCode 热题 HOT 100(05,正则表达式匹配)

    LeetCode 热题 HOT 100(05,正则表达式匹配) 不够优秀,发量尚多,千锤百炼,方可成佛. 算法的重要性不言而喻,无论你是研究者,还是最近比较火热的IT 打工人,都理应需要一定的算法能力 ...

  2. 🔥 LeetCode 热题 HOT 100(81-90)

    337. 打家劫舍 III 思路:后序遍历 + 动态规划 推荐题解:树形 dp 入门问题(理解「无后效性」和「后序遍历」) /** * Definition for a binary tree nod ...

  3. 🔥 LeetCode 热题 HOT 100(71-80)

    253. 会议室 II(NO) 279. 完全平方数 class Solution { public int numSquares(int n) { // dp[i] : 组成和为 i 的最少完全平方 ...

  4. 🔥 LeetCode 热题 HOT 100(51-60)

    142. 环形链表 II 思路:快慢指针,快慢指针相遇后,慢指针回到头,快慢指针步伐一致一起移动,相遇点即为入环点 /** * Definition for singly-linked list. * ...

  5. 🔥 LeetCode 热题 HOT 100(31-40)

    75. 颜色分类 思路:将 2 往后放,0 往前放,剩余的1自然就放好了. 使用双指针:left.right 分别指向待插入的 0 和 2 的位置,初始 left 指向数组头,right 指向数组尾部 ...

  6. 🔥 LeetCode 热题 HOT 100(21-30)

    46. 全排列 思路:典型回溯法 class Solution { public List<List<Integer>> permute(int[] nums) { Linke ...

  7. 🔥 LeetCode 热题 HOT 100(61-70)

    207. 课程表 思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能.因此可以将问题转换成: 判断有向图中是否存在环.使用 拓扑排序法 : 构建 入度表:记录每 ...

  8. 🔥 LeetCode 热题 HOT 100(41-50)

    102. 二叉树的层序遍历 思路:使用队列. /** * Definition for a binary tree node. * public class TreeNode { * int val; ...

  9. 🔥 LeetCode 热题 HOT 100(11-20)

    20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...

随机推荐

  1. NOIP模拟测试8「匹配·回家」

    匹配 哈希能A 水到爆炸 回家 事实上我做过一个原题,甚至比这个回家难的多,而且那个题多组询问必经点 然后我做一组询问就打炸了 大约就是删了很多东西,然后自己想的太简单了 直接统计了割点,懒得打lca ...

  2. 《吃透微服务》 - 服务容错之Sentinel

    大家好,我是小菜. 一个希望能够成为 吹着牛X谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤单! 本文主要介绍 SpringCloud中Sentinel 如有需要,可以参 ...

  3. android悬浮窗口

    悬浮窗原理 做过悬浮窗功能的人都知道, 要想显示悬浮窗, 要有一个服务运行在后台, 通过getSystemService(Context.WINDOW_SERVICE)拿到WindowManager, ...

  4. 日常BUG-01 之 @Sl4j

    问题描述: 需要打印日志,使用的是lombok包中的sl4j,lombok依赖如下: <dependency> <groupId>org.projectlombok</g ...

  5. http连接复用进化论

    HTTP协议是应用层协议,它定义万维网客户端如何与服务器进行通信.它在传输层的TCP协议的基础上进行数据传输 HTTP 1.0 在HTTP 1.0时代,默认一个http请求对应一个TCP连接,没有任何 ...

  6. 关于windows11的0x800f0950语言包安装失败

    最近windows11的风头很热,作为爱折腾的人,当然要去搞一搞啦.搞好了以后我发现中文语言的拓展包是无法安装的,于是我找到了3个办法,当然如果想100%成功的话我建议直接跳到第三个,如果你不嫌累,指 ...

  7. PE文件头格式解析

    前言: 昨天写了一题de1ctf的题,发现要脱壳,手脱之后发现要iat修复,我就发现自己在这块知识缺失了,win逆向,好像一直都是打ctf,然后用逆向方法论去肝的 其他方面倒是没有很深入学习,但实际上 ...

  8. Vue3 + Cesium + Typescript 集成搭建的快速启动模板(包含示例数据)

    开门见山 项目地址:https://github.com/tanghaojie/vue3-cesium-typescript-start-up-template 好用的话给个star呗,有更新可以第一 ...

  9. 安装PyTorch后,又安装TensorFlow,CUDA相关问题思考

    下面的话是我的观察和思考,请多多批评. TensorFlow 要用 CUDA.CUDA toolkit.CUDNN,看好版本的对应关系再安装,磨刀不误砍柴工. 1)NVIDIA Panel 里显示的N ...

  10. Maven项目导入Intellij IDEA

    目录 1. 自动创建maven项目 2. 修改IDEA默认远程仓库,提高依赖包下载速度 3. 修改IDEA中maven设置 4. 将maven项目导入IDEA 坑:IDEA无法下载依赖包 1. 自动创 ...