LeetCode刷题记录(中等题)
剑指 Offer 32 - I. 从上到下打印二叉树(4.20)
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
提示:
节点总数 <= 1000
1ms(BFS广度搜索,用队列)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int[] levelOrder(TreeNode root) {
if(root == null) return new int[0];
List<Integer> ans = new ArrayList();//存储答案
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
ans.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
int[] res = new int[ans.size()];
for(int i = 0;i<ans.size();i++){
res[i] = ans.get(i);
}
return res;
}
}
剑指 Offer 33. 二叉搜索树的后序遍历序列(4.20)
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000
0ms
class Solution {
// 要点:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是
public boolean verifyPostorder(int[] postorder) {
if (postorder.length < 2) return true;
return verify(postorder, 0, postorder.length - 1);
}
// 递归实现
private boolean verify(int[] postorder, int left, int right){
if (left >= right) return true; // 当前区域不合法的时候直接返回true就好
int rootValue = postorder[right]; // 当前树的根节点的值
int k = left;
while (k < right && postorder[k] < rootValue){ // 从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
k++;
}
for (int i = k; i < right; i++){ // 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接返回false
if (postorder[i] < rootValue) return false;
}
// 当前树没问题就检查左右子树
if (!verify(postorder, left, k - 1)) return false; // 检查左子树
if (!verify(postorder, k, right - 1)) return false; // 检查右子树
return true; // 最终都没问题就返回true
}
}
剑指 Offer 64. 求1+2+…+n(4.21)
求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
限制:
1 <= n <= 10000
1ms
class Solution {
public int sumNums(int n) {
try{
int i=1%n;
}catch (Exception e){
return 0;
}
return n+sumNums(n-1);
}
}
0ms
class Solution {
public int sumNums(int n) {
int sum = n;
boolean flag = n > 0 && (sum += sumNums(n - 1)) > 0;
return sum;
}
}
剑指 Offer 56 - II. 数组中数字出现的次数 II(4.21)
在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
13ms
class Solution {
public int singleNumber(int[] nums) {
Map<Integer,Integer> map=new HashMap<>();
for (int num : nums) {
if(map.containsKey(num)){
map.replace(num,map.get(num)+1);
}
else {
map.put(num,1);
}
}
for (Integer integer : map.keySet()) {
if(map.get(integer)==1){
return integer;
}
}
return 0;
}
}
48. 旋转图像(4.22)
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
- n == matrix.length == matrix[i].length
- 1 <= n <= 20
- -1000 <= matrix[i][j] <= 1000
0ms
class Solution {
//以四个顶点为关注点, 一个点的坐标是matrix[x][y],顺时针旋转就是:左上角y+1、右上角x+1,右下角y-1,左下角x-1。从外圈一圈一圈往里遍历交换。
public void rotate(int[][] matrix) {
int n = matrix.length - 1;
for (int i = 0; i <= matrix.length / 2; i++) {
for (int j = i; j < n - i; j++) {
// 获取各顶点的值
int a = matrix[i][j]; // 左上角
int b = matrix[j][n-i]; // 右上角
int c = matrix[n-i][n-j]; // 右下角
int d = matrix[n-j][i]; // 左下角
// 交换各顶点的值
matrix[i][j] = d; // 左上角
matrix[j][n-i] = a; // 右上角
matrix[n-i][n-j] = b; // 右下角
matrix[n-j][i] = c; // 左下角
}
}
}
}
剑指 Offer 35. 复杂链表的复制(4.22)
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 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 = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
-10000 <= Node.val <= 10000
Node.random
为空(null)或指向链表中的节点。- 节点数目不超过 1000 。
0ms(哈希表)
/*
// 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 head;
}
//map中存的是(原节点,拷贝节点)的一个映射
Map<Node, Node> map = new HashMap<>();
for (Node cur = head; cur != null; cur = cur.next) {
map.put(cur, new Node(cur.val));
}
//将拷贝的新的节点组织成一个链表
for (Node cur = head; cur != null; cur = cur.next) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
}
return map.get(head);
}
}
0ms(原地复制)
/*
// 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 head;
}
//将拷贝节点放到原节点后面,例如1->2->3这样的链表就变成了这样1->1'->2->2'->3->3'
for (Node node = head, copy = null; node != null; node = node.next.next) {
copy = new Node(node.val);
copy.next = node.next;
node.next = copy;
}
//把拷贝节点的random指针安排上
for (Node node = head; node != null; node = node.next.next) {
if (node.random != null) {
node.next.random = node.random.next; //node.next 是 node 复制出来的节点 ,node.random.next 也是 node.random 复制出来的节点
}
}
//分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
Node newHead = head.next;
for (Node node = head, temp = null; node != null && node.next != null;) {
// 要修改原来节点和copy节点各自的next
temp = node.next;
node.next = temp.next;
node = temp;
}
return newHead;
}
}
剑指 Offer 07. 重建二叉树(4.23)
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
示例 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
示例 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
限制:
0 <= 节点个数 <= 5000
2ms
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
/**
* 思路:通过先序遍历数组确定根节点,通过中序遍历确定左右孩子节点
* 1.使用HashMap存储中序遍历的节点和其索引
* 2.第一个节点就是根节点,通过map找到其在中序数组的位置,该节点左边的数是左子树,右边的数是右子树
* 3.递归寻找左子树的根节点、右子树的根节点
* @param preorder 先序遍历结果 array
* @param inorder 中序遍历结果 array
* @return 一棵二叉树
*/
HashMap<Integer, Integer> hm = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || inorder == null || preorder.length < 1 || inorder.length < 1){
return null;
}
for(int i = 0; i < inorder.length; i++){
hm.put(inorder[i], i);
}
return build(preorder, 0, 0, preorder.length - 1);
}
/**
*
* @param preorder 先序遍历数组
* @param index 根节点索引(先序遍历数组)
* @param l 子树的左边界 (中序遍历数组)
* @param r 子树的右边界(中序遍历数组)
* @return 二叉树节点
*/
public TreeNode build(int[] preorder, int index, int l, int r){
if(l > r){
return null;
}
TreeNode node = new TreeNode(preorder[index]);
Integer next = hm.get(preorder[index]);
node.left = build(preorder, index + 1, l, next - 1);
// 右子树的根节点可能不好理解 但其实就是把根节点的左子树的节点全部减去,就是右子树的根节点
node.right = build(preorder, index + (next - l + 1), next + 1, r);
return node;
}
}
剑指 Offer II 004. 只出现一次的数字 (4.23)
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,100]
输出:100
提示:
- 1 <= nums.length <= 3 * 10^4
- -2^31 <= nums[i] <= 2^31 - 1
- nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
5ms
class Solution {
public int singleNumber(int[] nums) {
Map<Integer, Integer> res = new HashMap<Integer, Integer>();
for (int num : nums) {
freq.put(num, res.getOrDefault(num, 0) + 1);
}
int ans = 0;
for (Map.Entry<Integer, Integer> entry : res.entrySet()) {
int num = entry.getKey(), occ = entry.getValue();
if (occ == 1) {
ans = num;
break;
}
}
return ans;
}
}
剑指 Offer II 055. 二叉搜索树迭代器(4.24)
实现一个二叉搜索树迭代器类BSTIterator
,表示一个按中序遍历二叉搜索树(BST)的迭代器:
-
BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字,且该数字小于 BST 中的任何元素。
-
boolean hasNext() 如果向指针右侧遍历存在数字,则返回 true ;否则返回 false 。
-
int next()将指针向右移动,然后返回指针处的数字。
注意,指针初始化为一个不存在于 BST 中的数字,所以对 next() 的首次调用将返回 BST 中的最小元素。
可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 的中序遍历中至少存在一个下一个数字。
示例:
输入
inputs = ["BSTIterator", "next", "next", "hasNext", "next", "hasNext", "next", "hasNext", "next", "hasNext"]
inputs = [[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], [], []]
输出
[null, 3, 7, true, 9, true, 15, true, 20, false]
解释
BSTIterator bSTIterator = new BSTIterator([7, 3, 15, null, null, 9, 20]);
bSTIterator.next(); // 返回 3
bSTIterator.next(); // 返回 7
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 9
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 15
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 20
bSTIterator.hasNext(); // 返回 False
提示:
- 树中节点的数目在范围
[1, 105]
内 0 <= Node.val <= 106
- 最多调用
105
次hasNext
和next
操作
15ms
/**
* 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 BSTIterator {
List<Integer> list;
int idx, n;
public BSTIterator(TreeNode root) {
list = new ArrayList<>();
LNR(root);
this.n = list.size();
idx = 0;
}
public int next() {
return list.get(idx++);
}
public boolean hasNext() {
if(idx<n) return true;
return false;
}
public void LNR(TreeNode node){
if(node == null) return ;
LNR(node.left);
list.add(node.val);
LNR(node.right);
}
}
/**
* Your BSTIterator object will be instantiated and called as such:
* BSTIterator obj = new BSTIterator(root);
* int param_1 = obj.next();
* boolean param_2 = obj.hasNext();
*/
*剑指 Offer II 079. 所有子集(4.24)
给定一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
0ms
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
dfs(nums,0);
return result;
}
public void dfs(int[] nums,int start) {
result.add(new ArrayList<>(res));
for (int i=start;i<nums.length;++i) {
res.add(nums[i]);
dfs(nums,i+1);
res.remove(res.size()-1);
}
}
}
剑指 Offer II 092. 翻转字符(4.25)
如果一个由 '0' 和 '1' 组成的字符串,是以一些 '0'(可能没有 '0')后面跟着一些 '1'(也可能没有 '1')的形式组成的,那么该字符串是 单调递增 的。
我们给出一个由字符 '0' 和 '1' 组成的字符串 s,我们可以将任何 '0' 翻转为 '1' 或者将 '1' 翻转为 '0'。
返回使 s 单调递增 的最小翻转次数。
示例 1:
输入:s = "00110"
输出:1
解释:我们翻转最后一位得到 00111.
示例 2:
输入:s = "010110"
输出:2
解释:我们翻转得到 011111,或者是 000111。
示例 3:
输入:s = "00011000"
输出:2
解释:我们翻转得到 00000000。
提示:
1 <= s.length <= 20000
- s 中只包含字符
'0'
和'1'
2ms(DP)
class Solution {
public int minFlipsMonoIncr(String s) {
int zero =0, one = 0;
for(char ch:s.toCharArray()){
if(ch=='1') one++;
else zero = Math.min(zero+1,one);
}
return zero;
}
}
剑指 Offer 36. 二叉搜索树与双向链表(4.26)
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
0ms
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
// 1. 中序,递归,来自解题大佬
Node pre, head;
public Node treeToDoublyList(Node root) {
// 边界值
if(root == null) return null;
dfs(root);
// 题目要求头尾连接
head.left = pre;
pre.right = head;
// 返回头节点
return head;
}
void dfs(Node cur) {
// 递归结束条件
if(cur == null) return;
dfs(cur.left);
// 如果pre为空,就说明是第一个节点,头结点,然后用head保存头结点,用于之后的返回
if (pre == null) head = cur;
// 如果不为空,那就说明是中间的节点。并且pre保存的是上一个节点,
// 让上一个节点的右指针指向当前节点
else if (pre != null) pre.right = cur;
// 再让当前节点的左指针指向父节点,也就连成了双向链表
cur.left = pre;
// 保存当前节点,用于下层递归创建
pre = cur;
dfs(cur.right);
}
}
剑指 Offer II 070. 排序数组中只出现一次的数字(4.27)
给定一个只包含整数的有序数组 nums
,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
示例 1:
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: nums = [3,3,7,7,10,11,11]
输出: 10
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^5
0ms
class Solution {
public int singleNonDuplicate(int[] nums) {
if(nums.length ==1){
return nums[0];
}
int l =0,r = nums.length-1;
while (l <= r){
if (nums[l+1] == nums[l]){
l = l+2;
}else {
return nums[l];
}
if (nums[r] == nums[r-1]){
r=r -2;
}else {
return nums[r];
}
}
return -1;
}
}
面试题 16.01. 交换数字(4.28)
编写一个函数,不用临时变量,直接交换numbers = [a, b]
中a
与b
的值。
示例:
输入: numbers = [1,2]
输出: [2,1]
提示:
numbers.length == 2
-2147483647 <= numbers[i] <= 2147483647
0ms
a=a^b;
b=a^b; // b= (a^b) ^ b 也就是 b= a^ (b ^ b ) 即 b=a ^ 0 , 即 b=a;
a=a^b; // 这个自己推, 注意 a 和b 都已经被修改过了.
假如给定 [1,3],也就是[01,11] 异或规则是同值为0,异值为1
numbers[0] = numbers[0]^numbers[1]; //按位异或运算赋值之后[10,11]
numbers[1] = numbers[0]^numbers[1]; //[10,01]
numbers[0] = numbers[0]^numbers[1]; //[11,01]也就是[3,1]
class Solution {
public int[] swapNumbers(int[] numbers) {
numbers[0] ^= numbers[1];
numbers[1] ^= numbers[0];
numbers[0] ^= numbers[1];
return numbers;
}
}
1325. 删除给定值的叶子节点(4.29)
给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。
注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。
也就是说,你需要重复此过程直到不能继续删除。
示例 1:
输入:root = [1,2,3,2,null,2,4], target = 2
输出:[1,null,3,null,4]
解释:
上面左边的图中,绿色节点为叶子节点,且它们的值与 target 相同(同为 2 ),它们会被删除,得到中间的图。
有一个新的节点变成了叶子节点且它的值与 target 相同,所以将再次进行删除,从而得到最右边的图。
示例 2:
输入:root = [1,3,3,3,2], target = 3
输出:[1,3,null,null,2]
示例 3:
输入:root = [1,2,null,2,null,2], target = 2
输出:[1]
解释:每一步都删除一个绿色的叶子节点(值为 2)。
示例 4:
输入:root = [1,1,1], target = 1
输出:[]
示例 5:
输入:root = [1,2,3], target = 1
输出:[1,2,3]
提示:
1 <= target <= 1000
- 每一棵树最多有
3000
个节点。 - 每一个节点值的范围是
[1, 1000]
。
0ms
后序遍历,因为需要根据子节点的结果,判断父节点是否需要删除
/**
* 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 TreeNode removeLeafNodes(TreeNode root, int target) {
if (root == null) return null;
root.left = removeLeafNodes(root.left, target);
root.right = removeLeafNodes(root.right, target);
if (root.val == target && root.left == null && root.right == null) return null;
return root;
}
}
109. 有序链表转换二叉搜索树(4.30)
给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。
示例 1:
输入: head = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
解释: 一个可能的答案是[0,-3,9,-10,null,5],它表示所示的高度平衡的二叉搜索树。
示例 2:
输入: head = []
输出: []
0ms
递归 - 快慢指针找中点
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if(head == null){
return null;
}
return helper(head,null);
}
private TreeNode helper(ListNode head,ListNode tail){
if(head == tail){
return null;
}
//找出当前链表中间位置(偶数个节点则取中间靠后那个节点), slow节点最终的位置即为中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != tail && fast.next != tail){
fast = fast.next.next;
slow = slow.next;
}
TreeNode root = new TreeNode(slow.val);
root.left = helper(head,slow);
root.right = helper(slow.next,tail);
return root;
}
}
/**
* 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; }
* }
*/
/**
* 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 TreeNode sortedListToBST(ListNode head) {
if(head == null) return null;
else if(head.next == null) return new TreeNode(head.val);
ListNode pre = head;
ListNode p = pre.next;
ListNode q = p.next;
//找到链表的中点p
while(q!=null && q.next!=null){
pre = pre.next;
p = pre.next;
q = q.next.next;
}
//将中点左边的链表分开
pre.next = null;
TreeNode root = new TreeNode(p.val);
root.left = sortedListToBST(head);
root.right = sortedListToBST(p.next);
return root;
}
}
92. 反转链表 II(5.1)
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
- 链表中节点数目为
n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
0ms
实现思路 :以1->2->3->4->5, m = 2, n=4 为例:
- 定位到要反转部分的头节点 2,head = 2;前驱结点 1,pre = 1;
- 当前节点的下一个节点3调整为前驱节点的下一个节点 1->3->2->4->5,
- 当前结点仍为2, 前驱结点依然是1,重复上一步操作。。。
- 1->4->3->2->5.
思路其实就是:我们需要反转n-m次,每一次我们将head的next节点移动到需要反转链表部分的首部,需要反转链表部分剩余节点依旧保持相对顺序即可
/**
* 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 reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
for(int i = 1; i < left; i++){
pre = pre.next;
}
head = pre.next;
for(int i = left; i < right; i++){
ListNode nex = head.next;
head.next = nex.next;
nex.next = pre.next;
pre.next = nex;
}
return dummy.next;
}
}
剑指 Offer II 038. 每日温度(5.2)
请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100
7ms
从后向前
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] arr = new int [temperatures.length];
for(int i = temperatures.length-1;i >= 0;i--){
int j = i + 1;
while(j < temperatures.length){
if(temperatures[j] > temperatures[i]){
arr[i] = j - i; // 计算几天后气温更高
break;
}else if(arr[j] == 0){ // 最后一天必为0,因为没有明天了
break;
}else{
j += arr[j]; // 如果此时天气温度没有大于前一天的,那么就找比此时温度高的那一天的下标偏移量arr[j],加到j上,就找出来比今天温度更高的那一天,进入下一次while循环,拿它来比较...
}
}
}
return arr;
}
}
148. 排序链表(5.3)
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目在范围
[0, 5 * 10^4]
内 -10^5 <= Node.val <= 10^5
7ms
/**
* 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 {
/**
* 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html
*
* 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的,
* 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并
* 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方
* 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后
* 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向
* 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点,
* 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。
*
* 主要考察3个知识点,
* 知识点1:归并排序的整体思想
* 知识点2:找到一个链表的中间节点的方法
* 知识点3:合并两个已排好序的链表为一个新的有序链表
*/
public ListNode sortList(ListNode head) {
return head == null ? null : mergeSort(head);
}
private ListNode mergeSort(ListNode head) {
if (head.next == null) {
return head;
}
ListNode p = head, q = head, pre = null;
while (q != null && q.next != null) {
pre = p;
p = p.next;
q = q.next.next;
}
pre.next = null;
ListNode l = mergeSort(head);
ListNode r = mergeSort(p);
return merge(l, r);
}
ListNode merge(ListNode l, ListNode r) {
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (l != null && r != null) {
if (l.val <= r.val) {
cur.next = l;
cur = cur.next;
l = l.next;
} else {
cur.next = r;
cur = cur.next;
r = r.next;
}
}
if (l != null) {
cur.next = l;
}
if (r != null) {
cur.next = r;
}
return dummyHead.next;
}
}
面试题 02.04. 分割链表(5.4)
给你一个链表的头节点 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
题目要求是只要把小于x的数移动到所有x的左方就行,没有顺序要求,而且等于x,大于x这些元素没有要求!
所以运用双指针,第一个指针落在不小于x的节点上,第二个向前移动。如果第二个指针遇到小于x的节点,交换二者的值,然后第一个指针指向next。第二个指针肯定走得更快,所以第一个指针一直处于不小于x的节点的位置,或者就是两个指针重叠,这时候还是礼貌性地交换一下罢了。
0ms
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
// [1, 4, 3, 2, 5, 2]. prev = 1, cur = 1
// [1, 4, 3, 2, 5, 2]. prev = 1, cur = 1 小于,交换二者,没有变化
// [1, 4, 3, 2, 5, 2]. prev = 4, cur = 4 继续往后next
// [1, 4, 3, 2, 5, 2]. prev = 4, cur = 3 无序交换, 只修改cur指针
// [1, 4, 3, 2, 5, 2]. prev = 4, cur = 2 同上
// [1, 2, 3, 4, 5, 2]. prev = 2, cur = 4 2 < 3,交换二者
// [1, 2, 3, 4, 5, 2]. prev = 3, cur = 5 二者同时往后next
// [1, 2, 3, 4, 5, 2]. prev = 3, cur = 2
// [1, 2, 2, 4, 5, 3] over
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode cur = head, prev = head;
while (cur != null) {
if (cur.val < x) {
int tmp = prev.val;
prev.val = cur.val;
cur.val = tmp;
prev = prev.next;
}
cur = cur.next;
}
return head;
}
}
剑指 Offer II 045. 二叉树最底层最左边的值(5.5)
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
提示:
- 二叉树的节点个数的范围是
[1,10^4]
-2^31 <= Node.val <= 2^31 - 1
1ms
保存每层第一个值就行了
/**
* 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 int findBottomLeftValue(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
int res = 0;
while (!deque.isEmpty()) {
int size = deque.size();
for (int i = 0; i < size; ++i) {
TreeNode node = deque.poll();
if (i == 0) {
res = node.val;
}
if (node.left != null) {
deque.offer(node.left);
}
if (node.right != null) {
deque.offer(node.right);
}
}
}
return res;
}
}
222. 完全二叉树的节点个数(5.6)
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1
提示:
- 树中节点的数目范围是
[0, 5 * 10^4]
0 <= Node.val <= 5 * 10^4
- 题目数据保证输入的树是 完全二叉树
0ms
/**
* 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 int countNodes(TreeNode root) {
/**
完全二叉树的高度可以直接通过不断地访问左子树就可以获取
判断左右子树的高度:
如果相等说明左子树是满二叉树, 然后进一步判断右子树的节点数(最后一层最后出现的节点必然在右子树中)
如果不等说明右子树是深度小于左子树的满二叉树, 然后进一步判断左子树的节点数(最后一层最后出现的节点必然在左子树中)
**/
if (root==null) return 0;
int ld = getDepth(root.left);
int rd = getDepth(root.right);
// (1 << ld)说明
// 1<<30为例,首先把1转为二进制数字 0000 0000 0000 0000 0000 0000 0000 0001
// 然后将上面的二进制数字向左移动30位后面补0得到 0010 0000 0000 0000 0000 0000 0000 0000
// (1 << ld)相当于 1 * 2^ld 次方, 满二叉树的节点个数计算公式为2^n-1(n为树的高度),即(1 << n) - 1
if(ld == rd) return (1 << ld) + countNodes(root.right); // 1(根节点) + (1 << ld)-1(左完全左子树节点数) + 右子树节点数量
else return (1 << rd) + countNodes(root.left); // 1(根节点) + (1 << rd)-1(右完全右子树节点数) + 左子树节点数量
}
private int getDepth(TreeNode r) {
int depth = 0;
while(r != null) {
depth++;
r = r.left;
}
return depth;
}
}
剑指 Offer II 085. 生成匹配的括号(5.7)
正整数 n
代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
提示:
1 <= n <= 8
1ms
class Solution {
List<String> result = new ArrayList<>();
public List<String> generateParenthesis(int n) {
String str = "";
dfs(n,n,str);
return result;
}
public void dfs(int left,int right,String s) {
if (right == 0 && left == 0) {
result.add(new String(s));
return;
}
// 保证不能超过括号对的数量,同时右括号的使用次数不能超过左括号的次数
if (left < 0 || right < 0 || right < left) {
return;
}
dfs(left-1,right,s+"(");
dfs(left,right-1,s+")");
}
}
260. 只出现一次的数字 III(5.7)
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
示例 1:
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0]
输出:[-1,0]
示例 3:
输入:nums = [0,1]
输出:[1,0]
提示:
- 2 <= nums.length <= 3 * 10^4
- -2^31 <= nums[i] <= 2^31 - 1
- 除两个只出现一次的整数外,nums 中的其他数字都出现两次
6ms (HashMap)
class Solution {
public int[] singleNumber(int[] nums) {
Map<Integer, Integer> freq = new HashMap<Integer, Integer>();
for (int num : nums) {
freq.put(num, freq.getOrDefault(num, 0) + 1);
}
int[] ans = new int[2];
int index = 0;
for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
if (entry.getValue() == 1) {
ans[index++] = entry.getKey();
}
}
return ans;
}
}
剑指 Offer II 060. 出现频率最高的 k 个数字(5.8)
给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
- 1 <= nums.length <= 10^5
- k 的取值范围是 [1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
14ms
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] ans=new int[k];
Map<Integer,Integer> map=new HashMap<>();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
List<Integer> list=new ArrayList<Integer>(map.keySet());
Collections.sort(list,(a,b)->map.get(b)-map.get(a)); // 倒序排序
for(int i=0;i<k;i++){
ans[i]=list.get(i);
}
return ans;
}
}
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
int res[] = new int[k];
for (int num: nums){
map.put(num,map.getOrDefault(num,0)+1);
}
ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
Collections.sort(list,(a,b)->b.getValue() - a.getValue()); // 倒序排序
for (int i =0 ; i < k ; i++){
res[i] = list.get(0).getKey();
list.remove(0);
}
return res;
}
}
剑指 Offer 56 - I. 数组中数字出现的次数(5.9)
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
nums = [1,2,10,4,1,4,3,3]
- a^a=0
- a^0=a
- a^b^c=a^c^b
- a&(-a)=最低位为1的二进制(从右到左)
- 所有的异或结果得到sum=2^10=8
- flag=-8&8=8
- 可分为两组,一组为与flag相与等于1的[10],另一组为0的[1,2,4,1,4,3,3]
- 组内异或分别得到【10】【2】
1ms
class Solution {
public int[] singleNumbers(int[] nums) {
int sum=0;
//得到异或结果,即为不相同两个数的异或结果sum
for(int num:nums)
sum^=num;
//得到sum的二进制的1的最低位
int flag=(-sum)∑
int result[]=new int[2];
//分成两个组进行异或,每组异或后的结果就是不相同两个数的其中之一
for(int num:nums){
if((flag&num)==flag)
result[0]^=num;
else{
result[1]^=num;
}
}
return result;
}
}
剑指 Offer 31. 栈的压入、弹出序列(5.10)
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
-
0 <= pushed.length == popped.length <= 1000
-
0 <= pushed[i], popped[i] < 1000
-
pushed 是 popped 的排列。
判断合不合法,用个栈试一试: 把压栈的元素按顺序压入,当栈顶元素和出栈的第一个元素相同,则将该元素弹出,出栈列表指针后移并继续判断。最后判断出栈列表指针是否指向出栈列表的末尾即可。
1ms
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new ArrayDeque<>();
int j = 0;
for (int elem : pushed){
stack.push(elem);
while(j < popped.length && !stack.isEmpty() && stack.peek() == popped[j]){
stack.pop();
j++;
}
}
return j == popped.length;
}
}
评论