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. Java-学习日记(Atomic,Volatile)

    很早之前在公司就看到了atomicInteger,atomicLong这些变量了,一直不明白是什么意思,今天花了点时间了解下. volatile: 先从volatile开始讲起,volatile是多线 ...

  2. [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer

    [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer 目录 [源码解析] 深度学习分布式训练框架 horovod (7) --- Distri ...

  3. AI框架中图层IR的分析

    摘要:本文重点分析一下AI框架对IR有什么特殊的需求.业界有什么样的方案以及MindSpore的一些思考. 本文分享自华为云社区<MindSpore技术专栏 | AI框架中图层IR的分析> ...

  4. .NET 云原生架构师训练营(设计原则与模式)--学习笔记

    在复杂系统的架构设计中引入设计原则与模式,能够极大降低复杂系统开发.和维护的成本 目录 几个问题 为什么要学习设计模式 优良架构设计的具体指标 理解复杂系统 面向对象思想(指导复杂系统的分析.设计.实 ...

  5. Java 创建PDF文件包的2种方法

    1. 概述 PDF文件包可方便在仅打开一个窗口的情况下阅读多个文档,通过将多个PDF文档或其他非PDF文档封装在一起,打开文件包后可以随意切换查看文件包中的文档,在需要编辑更改的情况,也可以打开文本包 ...

  6. POJ 2002 二分 计算几何

    根据正方形对角的两顶点求另外两个顶点公式: x2 = (x1+x3-y3+y1)/2; y2 = (x3-x1+y1+y3)/2; x4= (x1+x3+y3-y1)/2; y4 = (-x3+x1+ ...

  7. 1.6Java语言规范、API、JDK、和IDE

    要点提示:Java语言规范定义了Java的语法,Java库则在JavaAPI中定义.JDK是用于开发和运行Java程序的软件.IDE是快速开发程序的集成开发环境. 计算机语言有严格的使用规范.

  8. centos 8 安装 SonarQube遇到的启动问题及解决方案

    查看当前centos操作系统的版本 cat /etc/redhat-release 执行结果: 第一步安装openjdk,版本可以根据自己安装sonarqube的版本而定: 安装过程可参见我的另一篇博 ...

  9. AcWing 1293. 夏洛克和他的女朋友

    夏洛克有了一个新女友(这太不像他了!). 情人节到了,他想送给女友一些珠宝当做礼物. 他买了n件珠宝,第i件的价值是i+1. 华生挑战夏洛克,让他给这些珠宝染色,使得一件珠宝的价格是另一件珠宝的价格的 ...

  10. 『动善时』JMeter基础 — 54、JMeter聚合报告详解

    目录 1.聚合报告介绍 2.聚合报告界面详解 3.聚合报告中信息点说明 (1)百分位数的说明 (2)吞吐量说明 提示:聚合报告组件的使用和察看结果树组件的使用方式相同.本篇文章主要是详细的介绍一下聚合 ...