文献 哈希表与字符串 > 链表 > 二叉树与图 > 二分查找与二叉排序树 > 栈、队列、堆 > 其他(主要是一些数学问题)> 递归、回溯与分治 & 贪心算法 > 搜索 > 复杂数据结构 > 动态规划
一、哈希表与字符串
最长回文串(409)
题目:
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的 回文串 的长度。
在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。
示例 1:
输入: s = "abccccdd"
输出: 7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
示例 2:
输入: s = "a"
输出: 1
解释: 可以构造的最长回文串是"a",它的长度是 1。
提示:
1 <= s.length <= 2000s只由小写 和/或 大写英文字母组成
算法思路:
- 按照字符计算出,每个字符出现过多少次。
- 字符出现偶数次,必然是回文字符串,直接相加。
- 字符出现奇数次,相加偶数部分,奇数部分只能加一次。
java代码:
class Solution {
public int longestPalindrome(String s) {
Integer result = 0;
//计算字符出现次数
Map<Character, Integer> map = recordCounter(s);
for (Integer value : map.values()) {
//计算偶数出现次数
result += value / 2 * 2;
if (value % 2 == 1 && result % 2 == 0) {
//当字符出现奇数次时进入一次,让结果存在回文中心
result += 1;
}
}
return result;
}
private Map<Character, Integer> recordCounter(String s) {
int len = s.length();
Map<Character, Integer> map = new HashMap(len);
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
map.put(c, map.get(c) + 1);
} else {
map.put(c, 1);
}
}
return map;
}
}
单词规律(290)
题目:
给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s ****中的每个非空单词之间存在着双向连接的对应规律。
示例1:
输入: pattern = "abba", s = "dog cat cat dog"
输出: true
示例 2:
输入: pattern = "abba", s = "dog cat cat fish"
输出: false
示例 3:
输入: pattern = "aaaa", s = "dog cat cat dog"
输出: false
提示:
1 <= pattern.length <= 300pattern只包含小写英文字母1 <= s.length <= 3000s只包含小写英文字母和' 's不包含 任何前导或尾随对空格s中每个单词都被 单个空格 分隔
算法思路:
- 定义一个map保证pattern中的每一个字符都对应s中的每一个单词。
- 定义一个map保证s中的每一个单词都对应pattern中的每一个字符都。
java代码:
class Solution {
public boolean wordPattern(String pattern, String s) {
Map<Character, String> map = new HashMap<>();
Map<String, Character> map1 = new HashMap<>();
String[] strArry = s.split(" ");
Integer pLen = pattern.length();
if (strArry.length != pLen) {
return false;
}
for (int i = 0; i < pLen; i++) {
// 通过pattern找s,保证相同的pattern能找到相同的s
if (map.containsKey(pattern.charAt(i))) {
if (!Objects.equals(map.get(pattern.charAt(i)), strArry[i])) {
return false;
}
} else {
map.put(pattern.charAt(i), strArry[i]);
}
// 通过s找pattern,保证相同的s能找到相同的pattern
if (map1.containsKey(strArry[i])) {
if (!Objects.equals(map1.get(strArry[i]), pattern.charAt(i))) {
return false;
}
} else {
map1.put(strArry[i], pattern.charAt(i));
}
}
return true;
}
}
同字符词语分组(49)
题目:
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 1040 <= strs[i].length <= 100strs[i]仅包含小写字母
算法思路:
- 对每一个单词进行字符串排序,将排序后的字符串作为group的依据进行分类
java代码:
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
int len = strs.length;
Map<String, List<String>> map = new HashMap<>(strs.length);
for (int i = 0; i < len; i++) {
String sorted = sortedString(strs[i]);
if (map.containsKey(sorted)) {
map.get(sorted).add(strs[i]);
} else {
List<String> list = new ArrayList<>();
list.add(strs[i]);
map.put(sorted, list);
}
}
return map.values()
.stream()
.collect(Collectors.toList());
}
private String sortedString(String source) {
char[] chars = source.toCharArray();
// 使用 Arrays.sort() 对字符数组进行排序
Arrays.sort(chars);
// 将排序后的字符数组转换回字符串
return new String(chars);
}
}
无重复字符的最长子串(3)
题目:
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 ****的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。
提示:
0 <= s.length <= 5 * 104s由英文字母、数字、符号和空格组成
算法思路:
- 滑动窗口
java代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int start = 0;
int end = 0;
int max = 0;
int len = s.length();
while (start < len && end < len) {
end = start;
Map<Character, Integer> map = new HashMap<>();
for (; end < len; end++) {
if (map.containsKey(s.charAt(end))) {
break;
} else {
map.put(s.charAt(end), end);
}
}
int temp = end - start;
if (max < temp) {
max = temp;
}
if (end < len) {
start = map.get(s.charAt(end)) + 1;
}
}
return max;
}
}
重复的DNA序列(187)
题目:
DNA序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G' 和 'T'.。
- 例如,
"ACGAATTCCG"是一个 DNA序列 。
在研究 DNA 时,识别 DNA 中的重复序列非常有用。
给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。
示例 1:
输入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出: ["AAAAACCCCC","CCCCCAAAAA"]
示例 2:
输入: s = "AAAAAAAAAAAAA"
输出: ["AAAAAAAAAA"]
提示:
0 <= s.length <= 105s[i]``==``'A'、'C'、'G'or'T'
算法思路:
1.将整个dna序列按照10为长度切割成多块 2.用map记录每一块出现的次数,当出现次数为2时,则添加结果
java代码:
class Solution {
static final int T = 10;
public List<String> findRepeatedDnaSequences(String s) {
Map<String,Integer> dnaRecordMap = new HashMap<>();
List<String> result = new LinkedList<>();
for(int i =0;i<=s.length()-T;i++){
String dna = s.substring(i,i+T);
dnaRecordMap.put(dna,dnaRecordMap.getOrDefault(dna,0)+1);
if(dnaRecordMap.get(dna) == 2){
result.add(dna);
}
}
return result;
}
}
最小窗口子串(76)
题目:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
解释: 最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入: s = "a", t = "a"
输出: "a"
解释: 整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.lengthn == t.length1 <= m, n <= 105s和t由英文字母组成
进阶: 你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
算法思路:
动态窗口
java代码:
class Solution {
public String minWindow(String s, String t) {
if (s == null || s.isEmpty() || t == null || t.isEmpty() || s.length() < t.length())
return "";
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
// 初始化 need,记录 t 中每个字符的出现次数
for (char c : t.toCharArray())
need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0; // 窗口的左右边界
int valid = 0; // 已经匹配上的字符数量
int start = 0, minLen = Integer.MAX_VALUE; // 最小窗口的起始位置和长度
while (right < s.length()) {
char r = s.charAt(right);
right++;
// 更新窗口内字符的计数
if (need.containsKey(r)) {
window.put(r, window.getOrDefault(r, 0) + 1);
if (window.get(r).equals(need.get(r)))
valid++;
}
// 当窗口内的字符已经完全包含了 t 中的所有字符时
while (valid == need.size()) {
// 更新最小窗口的起始位置和长度
if (right - left < minLen) {
start = left;
minLen = right - left;
}
char l = s.charAt(left);
// 缩小窗口,移动左边界
if (need.containsKey(l)) {
window.put(l, window.get(l) - 1);
if (window.get(l) < need.get(l))
valid--;
}
left++;
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}
JZ5 替换空格
题目:
描述
请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
数据范围:0≤len(s)≤1000 0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。
示例1
输入:
"We Are Happy"
返回值:
"We%20Are%20Happy"
示例2
输入:
" "
返回值:
"%20"
算法思路:
StringBuilder聚合
java代码:
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return string字符串
*/
public String replaceSpace (String s) {
StringBuilder stringBuilder = new StringBuilder();
for(int i =0;i<s.length();i++){
if(s.charAt(i) == ' '){
stringBuilder.append("%20");
}else{
stringBuilder.append(s.charAt(i));
}
}
return stringBuilder.toString();
}
}
二、链表
K个一组翻转链表(25)
题目:
给你链表的头节点 head ,每 k **个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k **的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入: head = [1,2,3,4,5], k = 2
输出: [2,1,4,3,5]
示例 2:
输入: head = [1,2,3,4,5], k = 3
输出: [3,2,1,4,5]
提示:
- 链表中的节点数目为
n 1 <= k <= n <= 50000 <= Node.val <= 1000
进阶: 你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?
算法思路:
- 分组进行链表反转
- 衔接每一个反转后的结果
java代码:
/**
* 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 reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
for (int i = 0; i < k && end != null; i++)
end = end.next;
if (end == null)
break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;/* */
pre.next = reverse(start);
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
}
随机链表的复制(138)
题目:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示Node.val的整数。random_index:随机指针指向的节点索引(范围从0到n-1);如果不指向任何节点,则为null。
你的代码 只 接受原链表的头节点 head 作为传入参数。
示例 1:
输入: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出: [[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入: head = [[1,1],[2,1]]
输出: [[1,1],[2,1]]
示例 3:
输入: head = [[3,null],[3,0],[3,null]]
输出: [[3,null],[3,0],[3,null]]
else { if (curBig == null) { curBig = cur; bigHead = cur; } else { curBig.next = cur; curBig = cur; } } 提示:
0 <= n <= 1000-104 <= Node.val <= 104Node.random为null或指向链表中的节点。
算法思路:
链表复制必要元素:头节点、动态节点
java代码:
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
Node oriCur = head;
Node copyRoot = new Node(0);
Node copyCur = copyRoot;
Map<Node, Node> map = new HashMap<>();
while (oriCur != null) {
Node copyNext = new Node(oriCur.val);
map.put(oriCur, copyNext);
copyCur.next = copyNext;
oriCur = oriCur.next;
copyCur = copyCur.next;
}
copyCur = copyRoot.next;
oriCur = head;
while (oriCur != null) {
copyCur.random = map.get(oriCur.random);
oriCur = oriCur.next;
copyCur = copyCur.next;
}
return copyRoot.next;
}
}
86. 分隔链表
题目:
给你一个链表的头节点 head 和一个特定值 **x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例 1:
输入: head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入: head = [2,1], x = 2
输出:[1,2]
提示:
- 链表中节点的数目在范围
[0, 200]内 -100 <= Node.val <= 100-200 <= x <= 200
算法思路:
首先定于头节点,然后定义动态节点,串联拼接。
java代码:
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode small = new ListNode(0);
ListNode smallHead = small;
ListNode large = new ListNode(0);
ListNode largeHead = large;
while (head != null) {
if (head.val < x) {
small.next = head;
small = small.next;
} else {
large.next = head;
large = large.next;
}
head = head.next;
}
large.next = null;
small.next = largeHead.next;
return smallHead.next;
}
}
三、二叉树与图
113. 路径总和 II
题目:
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出: [[5,4,11,2],[5,8,4,5]]
示例 2:
输入: root = [1,2,3], targetSum = 5
输出: []
示例 3:
输入: root = [1,2], targetSum = 0
输出: []
提示:
- 树中节点总数在范围
[0, 5000]内 -1000 <= Node.val <= 1000-1000 <= targetSum <= 1000
算法思路:
depthFirstSearch 深度优先搜索
- 定义记录数组LinkedList.offerLast(a);pollLast()
- 函数返回为空的规则
- 获取满足要求的规则
java代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private List<List<Integer>> result = new LinkedList<>();
private Deque<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root, targetSum);
return result;
}
private void dfs(TreeNode root, int targetSum) {
if (root == null) {
return;
}
path.offerLast(root.val);
targetSum = targetSum - root.val;
if (root.left == null && root.right == null && targetSum == 0) {
result.add(new LinkedList<>(path));
}
dfs(root.left,targetSum);
dfs(root.right,targetSum);
path.pollLast();
}
}
236. 二叉树的最近公共祖先
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入: root = [1,2], p = 1, q = 2
输出: 1
提示:
- 树中节点数目在范围
[2, 105]内。 -109 <= Node.val <= 109- 所有
Node.val互不相同。 p != qp和q均存在于给定的二叉树中。
算法思路:
depthFirstSearch 深度优先搜索
- 终止条件
- 递归逻辑
- 返回值
java代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return dfs(root, p, q);
}
private TreeNode dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q)
return root;
TreeNode left = dfs(root.left, p, q);
TreeNode right = dfs(root.right, p, q);
if (left == null)
return right;
if (right == null)
return left;
return root;
}
}
114. 二叉树展开为链表
题目:
给你二叉树的根结点 root ,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode,其中right子指针指向链表中下一个结点,而左子指针始终为null。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入: root = [1,2,5,3,4,null,6]
输出: [1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入: root = []
输出: []
示例 3:
输入: root = [0]
输出: [0]
提示:
- 树中结点数在范围
[0, 2000]内 -100 <= Node.val <= 100
进阶: 你可以使用原地算法(O(1) 额外空间)展开这棵树吗?
算法思路:
dfs 深度优先搜索
java代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
dfs(root);
}
public TreeNode dfs(TreeNode root) {
if (root == null || (root.left == null && root.right == null)) {
return root;
}
TreeNode left = dfs(root.left);
TreeNode right = dfs(root.right);
if (left != null && right != null) {
left.right = root.right;
root.right = root.left;
root.left = null;
return right;
}
if (left != null) {
root.right = root.left;
root.left = null;
return left;
}
return right;
}
}
207. 课程表
题目:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入: numCourses = 2, prerequisites = [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入: numCourses = 2, prerequisites = [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
算法思路:
java代码:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] ans = new int[numCourses];//存每个结点的入度
List<List<Integer>> res = new ArrayList<>();//存结点之间依赖关系
Queue<Integer> queue = new LinkedList<>();
//初始化二维List集合
for(int i = 0; i < numCourses; i++)
res.add(new ArrayList<>());
//取出每一对结点
for(int[] temp : prerequisites){
ans[temp[0]]++;
res.get(temp[1]).add(temp[0]);
}
//先把所有入度为0的结点加入队列
for(int i = 0; i < numCourses; i++)
if(ans[i] == 0)
queue.add(i);
while(!queue.isEmpty()){
int pre = queue.poll();
numCourses--;
//根据依赖关系,把入度-1
for(int cur : res.get(pre)){
if(--ans[cur] == 0)
queue.add(cur);
}
}
return numCourses == 0;
}
}
四、二分查找与二叉排序树
35. 搜索插入位置
题目:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums为 无重复元素 的 升序 排列数组-104 <= target <= 104
算法思路:
java代码:
class Solution {
public int searchInsert(int[] nums, int target) {
return find(nums, 0, nums.length - 1, target);
}
public int find(int[] nums, int left, int right, int target) {
if (right - left < 0) {
return left;
}
int mid = (right + left) / 2;
if (nums[mid] < target) {
return find(nums, mid + 1, right, target);
} else {
return find(nums, left, mid - 1, target);
}
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
题目:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
示例 3:
输入: nums = [], target = 0
输出: [-1,-1]
提示:
0 <= nums.length <= 105-109 <= nums[i] <= 109nums是一个非递减数组-109 <= target <= 109
算法思路:
java代码:
class Solution {
public int[] searchRange(int[] nums, int target) {
int first = -1;
int end = -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
first = mid;
right = mid - 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
left = 0;
right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
end = mid;
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return new int[] { first, end };
}
}
五、栈、队列、堆
225. 用队列实现栈
题目:
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x)将元素 x 压入栈顶。int pop()移除并返回栈顶元素。int top()返回栈顶元素。boolean empty()如果栈是空的,返回true;否则,返回false。
注意:
- 你只能使用队列的标准操作 —— 也就是
push to back、peek/pop from front、size和is empty这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9- 最多调用
100次push、pop、top和empty - 每次调用
pop和top都保证栈不为空
进阶: 你能否仅用一个队列来实现栈。
算法思路:
java代码:
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
/** Initialize your data structure here. */
public MyStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue1.poll();
}
/** Get the top element. */
public int top() {
return queue1.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue1.isEmpty();
}
}
232. 用栈实现队列
题目:
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x)将元素 x 推到队列的末尾int pop()从队列的开头移除并返回元素int peek()返回队列开头的元素boolean empty()如果队列为空,返回true;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top,peek/pop from top,size, 和is empty操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
-
1 <= x <= 9 -
最多调用
100次push、pop、peek和empty -
假设所有操作都是有效的 (例如,一个空的队列不会调用
pop或者peek操作 进阶: -
你能否实现每个操作均摊时间复杂度为
O(1)的队列?换句话说,执行n个操作的总时间复杂度为O(n),即使其中一个操作可能花费较长时间。
算法思路:
java代码:
class MyQueue {
Deque<Integer> inStack;
Deque<Integer> outStack;
public MyQueue() {
inStack = new ArrayDeque<Integer>();
outStack = new ArrayDeque<Integer>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.pop();
}
public int peek() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void in2out() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
155. 最小栈
题目:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack()初始化堆栈对象。void push(int val)将元素val推入堆栈。void pop()删除堆栈顶部的元素。int top()获取堆栈顶部的元素。int getMin()获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
-231 <= val <= 231 - 1pop、top和getMin操作总是在 非空栈 上调用push,pop,top, andgetMin最多被调用3 * 104次
算法思路:
用栈可以保存最小值minStack.push(Math.min(minStack.peek(), x));
java代码:
class MinStack {
private List<Integer> list;
public MinStack() {
this.list = new ArrayList<>();
}
public void push(int val) {
list.add(val);
}
public void pop() {
if (list.size() > 0)
list.remove(list.size() - 1);
}
public int top() {
return list.get(list.size() - 1);
}
public int getMin() {
return Collections.min(list);
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
其他算法
190. 颠倒二进制位
题目:
颠倒给定的 32 位无符号整数的二进制位。
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数
-3,输出表示有符号整数-1073741825。 示例 1:
输入: n = 00000010100101000001111010011100
输出: 964176192 (00111001011110000010100101000000)
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596 , 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入: n = 11111111111111111111111111111101
输出: 3221225471 (10111111111111111111111111111111)
解释: 输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
算法思路:
java代码:
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int res = 0;
for (int i = 0; i < 32 && n != 0; i++) {
res = res | ((n & 1) << (31 - i));
n = n >>> 1;
}
return res;
}
}
1206.设计跳表
题目:
不使用任何库函数,设计一个 跳表 。
跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。
例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 80、45 到跳表中,以下图的方式操作:
跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)。
了解更多 : oi-wiki.org/ds/skiplist…
在本题中,你的设计应该要包含这些函数:
bool search(int target): 返回target是否存在于跳表中。void add(int num): 插入一个元素到跳表。bool erase(int num): 在跳表中删除一个值,如果num不存在,直接返回false. 如果存在多个num,删除其中任意一个即可。
注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。
算法思路:
java代码:
链表
无重复字符的最长子串(3)
题目:
算法思路:
java代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Skiplist {
private Node<Integer> head;
private static final int MAX_LEVEL = 4;
private final Random random = new Random();
public Skiplist() {
this.head = new Node<>(null, MAX_LEVEL);
}
public boolean search(int target) {
Node<Integer> current = head;
for (int i = MAX_LEVEL - 1; i >= 0; i--) {
while (current.getForword().size() > i && current.getForword().get(i) != null
&& current.getForword().get(i).getValue() < target) {
current = current.getForword().get(i);
}
}
if (current.getForword().size() > 0) {
current = current.getForword().get(0);
}
return current != null && current.getValue() != null && current.getValue() == target;
}
public void add(int num) {
Node<Integer> current = head;
Node<Integer>[] update = new Node[MAX_LEVEL];
for (int i = MAX_LEVEL - 1; i >= 0; i--) {
while (current.getForword().size() > i && current.getForword().get(i) != null
&& current.getForword().get(i).getValue() < num) {
current = current.getForword().get(i);
}
update[i] = current;
}
int level = randomLevel();
Node<Integer> newNode = new Node<>(num, level);
for (int i = 0; i < level; i++) {
newNode.getForword().add(update[i].getForword().get(i));
update[i].getForword().set(i, newNode);
}
}
public boolean erase(int num) {
Node<Integer> current = head;
Node<Integer>[] update = new Node[MAX_LEVEL];
for (int i = MAX_LEVEL - 1; i >= 0; i--) {
while (current.getForword().size() > i && current.getForword().get(i) != null
&& current.getForword().get(i).getValue() < num) {
current = current.getForword().get(i);
}
update[i] = current;
}
if (current.getForword().size() > 0) {
current = current.getForword().get(0);
}
if (current != null && current.getValue() == num) {
for (int i = 0; i < MAX_LEVEL; i++) {
if (update[i].getForword().size() > i && update[i].getForword().get(i) == current) {
update[i].getForword().set(i, current.getForword().size() > i ? current.getForword().get(i) : null);
}
}
return true;
}
return false;
}
static class Node<T> {
private T value;
private final List<Node<T>> forword;
public Node(T value, int level) {
this.value = value;
this.forword = new ArrayList<>();
for (int i = 0; i < level; i++) {
this.forword.add(null);
}
}
public T getValue() {
return value;
}
public List<Node<T>> getForword() {
return forword;
}
}
private int randomLevel() {
int level = 1;
while (level < MAX_LEVEL && random.nextDouble() < 0.5) {
level++;
}
return level;
}
}
链表
无重复字符的最长子串(3)
题目:
算法思路:
java代码:
链表
无重复字符的最长子串(3)
题目:
算法思路:
java代码: