学习计划广场
学习计划广场
剑指Offer
剑指Offer
第 1 天 栈与队列 (简单)
解题思路:
难点
:花 5 分钟才看懂题目的输入。。。
- 首先,我们要知道队列的特性是
先进先出
,只能在队头做删除操作;而栈的特性是先进后出
,只能在一端(称为栈顶 (top) )对数据项进行插入和删除。 - 我们要做到的是,队列队头(即栈底)做删除操作,队列队尾(即栈顶)做插入操作。
- 因为 Stack 类中有
push()
方法,用栈实现尾部插入整数很简单,但要模拟在队列头部删除整数的功能,在同一个栈是做不到的,所以这里需要两个栈来模拟队列。
以下是 Stack 类的一些方法:
方法 | 方法描述 |
---|---|
boolean empty() | 测试堆栈是否为空。 |
Object peek( ) | 查看堆栈顶部的对象,但不从堆栈中移除它。 |
Object pop( ) | 移除堆栈顶部的对象,并作为此函数的值返回该对象。 |
Object push(Object element) | 把项压入堆栈顶部。 |
辅助栈 stack2)存储待删除的元素):将 stack1 的元素一个个弹出插入到 stack2,此时 stack1 底部的元素就到了 stack2 的栈顶(倒序),stack2 调用pop()
方法即可实现删除头部整数。
CQueue
(初始化):两个栈的初始化。
appendTail
(在队列尾部插入整数即 stack1 入栈):直接向 stack1 插入。
deleteHead
(在队列头部删除整数即 stack2 出栈):
- 如果 stack1、stack2 都为空,则直接返回 -1,否则 stack1、stack2 必有一个不为空。
- 如果只有 stack2 为空,将 stack1 的元素一个个弹出插入到 stack2
- 到了第三步,经过一二步骤的判断,此时 stack2 里不为空,我们直接再弹出 stack2 栈顶元素。
代码
class CQueue {
// 题目说了 两个栈 stack1 存储元素,方便尾部插入,stack2 存储队列的倒序,方便删除
private Stack<Integer> stack1, stack2;
// 初始化
public CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
// stack1 存储元素
public void appendTail(int value) {
stack1.push(value);
}
// 移除元素
public int deleteHead() {
// 两个栈为空
if(stack1.empty() && stack2.empty()){
return -1;
}
// stack1 不为空
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
解题思路:
这道题的重点是求出栈里元素的最小值,因为 Stack 类并没有提供函数,每次采用对 Stack 集合遍历方式,时间复杂度就不太理想了。
而题目要求调用 min
、push
及 pop
的时间复杂度都是 O(1)。
一般情况下,栈的 push
和 pop
的时间复杂度就是 O(1)了,所以我们只需要以 O(1) 的时间复杂度查找栈中的最小值。
- 刚开始思路:打算用一个变量存放当前最小值,每次
push
更新,但是当我的pop
掉的值刚好是当前最小值的时候,变量就没有参照价值了,后面想到用辅助栈 stack2 保存最小值。 - 辅助栈 stack2 来存储每次 push 进新元素时对应的最小值,如果最小值小于等于(题目没说 push 进去的元素不能重复) stack2 栈顶对应的元素(表示 stack 栈中的最小值),那么就
push
进 stack2 。出栈时如果pop 掉的值刚好是当前最小值的时候,也要pop
掉辅助栈 stack2 的栈顶元素。
注意点:
在本题 pop
方法中,来判断 stack1 的栈顶元素是否与 stack1 的栈顶元素是否相等。如果用 ==
将会无法通过。(看了榜一的题解才了解到o(╥﹏╥)o)
因为对于某些值,s1.push(x) 会转化为 s1.push(Integer.valueOf(x)),然后会利用 cache 的值,导致实例复用。 Integer 的 equals 重写过,比较的是内部 value 的值, == 如果在 [-128,127] 会被 cache 缓存,超过这个范围则比较的是对象是否相同。
代码
class MinStack {
// stack1 存储栈, stack2 栈顶存储栈的最小值
private Stack<Integer> stack1, stack2;
/** initialize your data structure here. */
public MinStack() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
// stack1 已经空了 或者 stack2 原最小值大于等于 x
if(stack2.empty() || stack2.peek() >= x){
stack2.push(x);
}
stack1.push(x);
}
public void pop() {
if(stack1.pop().equals(stack2.peek())){
stack2.pop();
}
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.min();
*/
第 2 天 链表 (简单)
解题思路:
- 遍历,获取链表长度
- 创建数组(数组空间为链表长度),向数组反向填充链表的
val
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
int len = 0;
ListNode node1 = head, node2 = head;
while(node1 != null) {
len ++;
node1 = node1.next;
}
int[] result = new int[len];
for(int i = len - 1; i >= 0; i --) {
result[i] = node2.val;
node2 = node2.next;
}
return result;
}
}
解题思路:
这里讲解的反转链表更全面: https://blog.csdn.net/qq_36903042/article/details/105576141
这里用的是 迭代法
。
在遍历链表时,我们需要三个指针,第一个指针 cur
指向当前头结点 head
,第二个指针 node
指向 cur
(当前结点),第三个指针为存储后一个节点的临时节点 temp
。
-
先保存下一节点。
-
然后让当前结点的指向前一节点。
-
最后移动当前结点到下一结点。(head 头节点一开始初始化指向 null)。
-
重复该循环。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode node = null, cur = head, temp = null;
while(cur != null) {
temp = cur.next;
cur.next = node;
node = cur;
cur = temp;
}
return node;
}
}
解题思路:
?> 后续再补,没搞懂
第 3 天 字符串 (简单)
解题思路:
- 使用 StringBuilder 拼接 ,遍历字符串碰到空格则替换
%20
,否则直接append
代码
class Solution {
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < s.length(); ++ i) {
char ch = s.charAt(i);
if(ch == ' ') {
sb.append("%20");
}else {
sb.append(ch);
}
}
return sb.toString();
}
}
解题思路:
java api 中调用 substring
截取字符串前后两段
代码
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n) + s.substring(0, n);
}
}
第 4 天 查找算法 (简单)
解题思路:
- 第一种解法比较容易想到,先把输入的数组排序,遍历排序后的数组,直到遍历该下标元素与前一个元素重复即可。
- 第二种解法可以用哈希表。遍历整个数组,判断该数组元素是否已经在哈希表中,如果没有就加入到哈希表,如果哈希表已经存在该元素,即可返回该重复结果。
- 第三种解法是达到了另类的字典目的,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应。来自**《剑指 Offer——名企面试官精讲典型编程题》第二版** 39页 💯
-
代码
第一种解法
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
int i = 0;
for(i = 1; i < nums.length; ++ i) {
if(nums[i] == nums[i - 1]) {
break;
}
}
return nums[i];
}
}
第二种解法
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int i = 0; i < nums.length; ++ i) {
if(set.contains(nums[i])){
return nums[i];
}
set.add(nums[i]);
}
return -1;
}
}
第三种解法💯
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i = 0; i < nums.length; ++ i) {
while(nums[i] != i) {
if(nums[i] == nums[nums[i]]) {
return nums[i];
}
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}
解题思路:
二分查找算法,递归找出最左边、最右边的 target
。
代码
class Solution {
public int search(int[] nums, int target) {
if(nums != null || nums.length > 0) {
int left = getFirstVal(nums, 0, nums.length - 1, target);
int right = getLastVal(nums, 0, nums.length - 1, target);
if(left > -1 && right > -1) {
return right - left + 1;
}
}
return 0;
}
public int getFirstVal(int[] nums, int start, int end, int target) {
if(start > end) {
return -1;
}
int mid = (start + end) / 2;
if(nums[mid] == target) {
if(mid > 0 && nums[mid - 1] != target || mid == 0){
return mid;
}else {
end = mid - 1;
}
}else if(nums[mid] > target) {
end = mid - 1;
}else {
start = mid + 1;
}
return getFirstVal(nums, start, end, target);
}
public int getLastVal(int[] nums, int start, int end, int target) {
if(start > end) {
return -1;
}
int mid = (start + end) / 2;
if(nums[mid] == target) {
if(mid < nums.length - 1 && nums[mid + 1] != target || mid == nums.length - 1){
return mid;
}else {
start = mid + 1;
}
}else if(nums[mid] > target) {
end = mid - 1;
}else {
start = mid + 1;
}
return getLastVal(nums, start, end, target);
}
}
解题思路:
二分查找。
0~n-1
这些数字在数组中都是排序的,因此数组中开始的一些数字与它们的下标相同。
不在数组中的那个数字记为 left
,left
左边的所有数字与它们的下标相同。
代码
class Solution {
public int missingNumber(int[] nums) {
if(nums == null || nums.length < 0) {
return -1;
}
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = (left + right) >> 1;
if(nums[mid] == mid) {
left = mid + 1;
}else{
right = mid - 1;
}
}
return left;
}
}
第 5 天 查找算法 (中等)
解题思路:
从 [0, matrix[0].length - 1]
右上角开始比较。
- 如果当前位置元素比
target
小,则这一行可以排除,往下数一行比较 - 如果当前位置元素比
target
大,则这一列可以排除,往左数一列比较 - 如果相等,返回
true
- 如果越界了还没找到,说明不存在,返回
false
代码
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 ||matrix[0].length == 0) {
return false;
}
int n = matrix.length, m = matrix[0].length;
int tmpN = 0, tmpM = m - 1;
while(tmpN < n && tmpM >= 0) {
if(matrix[tmpN][tmpM] == target) {
return true;
}else if(matrix[tmpN][tmpM] > target) {
tmpM --;
}else {
tmpN ++;
}
}
return false;
}
}
解题思路:
题目有标注输入的数组是递增排序的数组,此时它的最小值就是第一个元素。
进行旋转,把一个数组最开始的若干个元素搬到数组的末尾。>=
最小值 <=
,考虑数组元素重复的情况。
二分查找。
- 如果中间元素大于右边元素,证明 所求最小值就在右半部分
- 如果中间元素小于右边元素,证明 所求最小值就在左半部分(递增数组,最小值左边的元素都
>=
右边元素)。
代码
class Solution {
public int minArray(int[] numbers) {
if(numbers == null || numbers.length < 0) {
return -1;
}
int start = 0, end = numbers.length - 1;
while(start < end) {
int mid = (end + start) / 2;
if(numbers[mid] < numbers[end]){
end = mid;
}else if(numbers[mid] > numbers[end]){
start = mid + 1;
}else {
end --;
}
}
return numbers[start];
}
}
解题思路:
第一个只出现一次的字符:下面我用的是 HashMap
,不保证顺序,想要保证顺序可以使用 LinkedHashMap
去实现。
- 遍历字符串,用哈希表判断数量
- 遍历到的元素,若在哈希表存在,哈希表对应的值变为
false
(不满足数量为1
);若在哈希表中不存在该键,加入,哈希表对应的值设为true
- 遍历到的元素,若在哈希表存在,哈希表对应的值变为
- 第二次遍历字符串,若字符在哈希表中的键为
true
,返回该字符。遍历结束,没有则返回' '
。
代码
class Solution {
public char firstUniqChar(String s) {
Map<Character, Boolean> map = new HashMap<>();
char[] ch = s.toCharArray();
for(int i = 0; i < ch.length; ++ i) {
if(map.containsKey(ch[i])){
map.put(ch[i], false);
}else {
map.put(ch[i], true);
}
}
for(int i = 0; i < ch.length; ++ i) {
if(map.get(ch[i])) {
return ch[i];
}
}
return ' ';
}
}
第 6 天 搜索与回溯算法 (简单)
解题思路:
就层序遍历。
代码
/**
* 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];
}
int[] result;
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> list = new ArrayList<>();
queue.add(root);
while(!queue.isEmpty()) {
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left != null) {
queue.add(temp.left);
}
if(temp.right != null) {
queue.add(temp.right);
}
}
result = new int[list.size()];
int i = 0;
for(Integer index : list) {
result[i ++] = index;
}
return result;
}
}
解题思路:
按层遍历。每次遍历,记录这一层的个数,临时列表 temp
去存储当前层打印结果,直到该层个数为 0。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) {
return new ArrayList<List<Integer>>();
}
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> list = new ArrayList<>();
queue.add(root);
while(!queue.isEmpty()) {
List<Integer> temp = new ArrayList<>();
for(int i = queue.size(); i > 0; i --) {
TreeNode node = queue.poll();
temp.add(node.val);
if(node.left != null) {
queue.add(node.left);
}
if(node.right != null) {
queue.add(node.right);
}
}
list.add(temp);
}
return list;
}
}
解题思路:
跟上一题类似,都是每一层单独作为一行打印。
不同的是,以根节点(第一层)开始,奇数层从左到右打印,偶数行从右到左打印。
所以我们可以在之前基础上借由 boolean flag
布尔变量值 去判断此时的层的奇偶数:每次遍历完一行,状态改变 :flag = !flag;
。
- 只有当 flag 为 true,证明此层是偶数行,使用
Collections.reverse(temp);
反转临时列表元素。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) {
return new ArrayList<>();
}
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> list = new ArrayList<>();
queue.add(root);
boolean flag = false;
while(!queue.isEmpty()) {
List<Integer> temp = new ArrayList<>();
for(int i = queue.size(); i > 0; i --) {
TreeNode node = queue.poll();
temp.add(node.val);
if(node.left != null) {
queue.add(node.left);
}
if(node.right != null) {
queue.add(node.right);
}
}
if(flag){
Collections.reverse(temp);
}
flag = !flag;
list.add(temp);
}
return list;
}
}
第 7 天 搜索与回溯算法 (简单)
解题思路:
DFS。
- isSubTree():函数判断A存在一部分和B相等
- B 为空,比较结束,A 存在一部分和 B 相等,true。
终止条件
- B 不为空,A 已经为空或者 A结点的值与 B 结点的值不相等,两者不匹配,false。
终止条件
- 都不是,就继续递归判断,判断 A 和 B 的左子节点是否相等(判断 A 和 B 的右子节点是否相等)
- B 为空,比较结束,A 存在一部分和 B 相等,true。
- 递归遍历A树的每一个节点作为根结点和B树进行比较
- 不符合就比较左侧的,判断 A 的左子节点和 B 是否相等。
- 不符合就比较右侧的,判断 A 的右子节点和 B 是否相等。
- 只要当前结点,左侧,右侧最终匹配成功,都可以判断 B 是 A 的子结构。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null) {
return false;
}
if(B == null) {
return false;
}
boolean result = isSubTree(A, B);
boolean res = isSubStructure(A.left, B) || isSubStructure(A.right, B);
return result || res;
}
public boolean isSubTree(TreeNode A, TreeNode B) {
if(B == null) {
return true;
}
if(A == null || A.val != B.val) {
return false;
}
return isSubTree(A.left, B.left) && isSubTree(A.right, B.right);
}
}
解题思路:
每个结点的左右结点交换位置。
递归,借助临时节点 temp
来交换左右结点。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) {
return null;
}
TreeNode temp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
}
解题思路:
从根节点向下逐步递归,判断各左右节点是否对称,从而判断整棵树树是否为对称二叉树。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
// 树为空,肯定对称
if(root == null) {
return true;
}
// 判断左右结点是否对称
boolean res = ismirror(root.left, root.right);
return res;
}
public boolean ismirror(TreeNode Tleft, TreeNode Tright) {
// 两树为空,肯定对称
if(Tleft == null && Tright == null) {
return true;
}
// 其中一棵树为空,或者不对称:两树当前结点值不相等
if(Tleft == null || Tright == null || Tleft.val != Tright.val) {
return false;
}
// 两树结点均不为空,递归
boolean res = ismirror(Tleft.left, Tright.right) && ismirror(Tleft.right, Tright.left);
return res;
}
}
第 8 天 动态规划 (简单)
解题思路:
从 斐波那契数列性质 f(n) = f(n - 1) + f(n - 2) 入手。
动态规划。
代码
class Solution {
public int fib(int n) {
if(n == 0 || n == 1) {
return n;
}
int a = 0, b = 1;
for(int i = 0; i < n; ++ i) {
int sum = (a + b) % 1000000007;
a = b % 1000000007;
b = sum;
}
return a;
}
}
解题思路:
逻辑和斐波那契数列是一致的。
代码
class Solution {
public int numWays(int n) {
if(n <= 1) {
return 1;
}
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; ++ i){
dp[i] = (dp[i-1] + dp[i-2]) % 1000000007;
}
return dp[n];
}
}
解题思路:
d[i] 指的前 i 天所能得到的最大利润。
双 for 暴力解法。
代码
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[prices.length + 1];
dp[0] = 0;
for(int i = 0; i < prices.length; ++ i) {
int cost = 0;
for(int j = i + 1; j < prices.length; ++ j) {
cost = Math.max(prices[j] - prices[i], cost);
}
dp[i + 1] = Math.max(dp[i], cost);
}
return dp[prices.length];
}
}
第 9 天 动态规划 (中等)
解题思路:
单元格只可能是从上边或左边到达。上一步加上这个单元格的价值。
状态转移方程:dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
代码
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m + 1][n + 1];
for(int i = 1; i <= m; ++ i) {
for(int j = 1; j <= n; ++ j) {
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
}
}
return dp[m][n];
}
}
解题思路:
dp[i] 指的 以 num[i] 为结尾的连续子数组最大和
,保存子问题的解。
若 nums[i] + dp[i - 1] 反而变小,还不如从 nums[i] 开始计算。
代码
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int maxTemp = dp[0];
for(int i = 1; i < nums.length; ++ i) {
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
maxTemp = Math.max(maxTemp, dp[i]);
}
return maxTemp;
}
}
第 10 天 动态规划 (中等)
解题思路:
dp[i] 代表以 下标 i 为结尾前的数字的翻译方案数量。
- 如果连续值不能被整体翻译(不属于[10, 25]),那么它们的翻译方案只有一种。
- 如果连续值能被整体翻译,那么它们的翻译方案有两种。
应该。。
像之前的青蛙跳台阶,在[10, 25]范围的话一次可以爬一级或爬两级,其他的只能爬一级。
代码
class Solution {
public int translateNum(int num) {
// 数字转字符串
String str = String.valueOf(num);
int len = str.length();
int[] dp = new int[len + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= str.length(); ++ i) {
String temp = str.substring(i - 2, i);
// 分情况
// 1. 连续两位数字在 [10, 25]内可以组合翻译,也可以单独翻译 -> 就是2种翻译方法
// 2. 不在其中,单独翻译 -> 就是1种翻译方法
if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0){
dp[i] = dp[i - 1] + dp[i - 2];
}else {
dp[i] = dp[i - 1];
}
}
return dp[dp.length - 1];
}
}
解题思路:
滑动窗口
的概念。
双指针 + 哈希表。
哈希表:遍历,统计各字符最后一次出现的索引位置,遇到重复,记录左指针(重复字符在哈希表中对应的值),并在哈希表更新该字符现在的下标位置。
左指针 - 当前下标,就是滑动窗口的长度。如果大于 res,就需要进行更新。
代码
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
// 使用哈希表 统计各字符最后一次出现的索引位置
Map<Character, Integer> map = new HashMap<>();
int res = 0, index = -1;
for(int i = 0; i < n; ++ i) {
char ch = s.charAt(i);
if(map.containsKey(ch)){
index = Math.max(index, map.get(ch));
}
map.put(ch, i);
res = Math.max(res, i - index);
}
return res;
}
}
第 11 天 双指针 (简单)
解题思路:
cur = head,cur 指向当前节点
cur = cur.next,进行链表遍历,直到找到要删除节点,跳过,指向待删除节点的下一个指向。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head == null) {
return null;
}
if(head.val == val) {
return head.next;
}
ListNode cur = head;
ListNode pre = null;
// 记录要删除结点
while(cur.next != null){
if(cur.next.val == val) {
break;
}
cur = cur.next;
}
if(cur.next != null) {
cur.next = cur.next.next;
}
return head;
}
}
解题思路:
我自己想到的是 顺序遍历,直到 len - k
个节点返回即可。
看了题目评论(新天地):快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。
刷题还是不够。。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode cur1 = head, cur2 = head;
while(cur1 != null && k-- > 0) {
cur1 = cur1.next;
}
while(cur1 != null) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur2;
}
}
第 12 天 双指针 (简单)
解题思路:
声明哑结点、头节点,当两个链表都不为空时,让链表 l1、l2
节点的值进行比较,哪个小就让头节点先指向谁,一直遍历到存在任意一个链表为空,跳出循环。
又链表 l1、l2
是 递增 的,若某个链表剩下还有的,直接让头节点的next指向剩下的
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) {
return l2;
}
if(l2 == null) {
return l1;
}
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while(l1 != null && l2 != null) {
if(l1.val >= l2.val) {
cur.next = l2;
l2 = l2.next;
}else {
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
if(l1 != null) {
cur.next = l1;
}
if(l2 != null) {
cur.next = l2;
}
return dummyHead.next;
}
}
解题思路:
是要求指针相等,而不是第一个相等的数字。
先求出两个链表的长度,长度较长的链表再走相差的长度,让链表处于同一起跑线。
然后遍历两个链表遇到相同则跳出遍历。
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA, B = headB;
int lenA = 0, lenB = 0;
while (A != null) {
++ lenA;
A = A.next;
}
while (B != null) {
++ lenB;
B = B.next;
}
A = headA;
B = headB;
int temp = Math.abs(lenA - lenB);
if(lenA > lenB) {
while(temp-- > 0) A = A.next;
}else {
while(temp-- > 0) B = B.next;
}
while(A != B) {
A = A.next;
B = B.next;
}
return A;
}
}
第 13 天 双指针 (简单)
解题思路:
双指针。
左指针找偶数,右指针找奇数,两边交换。
直到指针左边都是奇数,指针右边都是偶数
代码
class Solution {
public int[] exchange(int[] nums) {
int left = 0, right = nums.length - 1;
while(left < right) {
while(left < right && (nums[left] & 1) != 0) {
left ++;
}
while(left < right && (nums[right] & 1) == 0) {
right --;
}
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
return nums;
}
}
解题思路:
双指针。
题目有注明 :递增数组。
可以使用双指针左右夹击。
- 若左右指针对应的索引的值的和等于 target,返回。
- 若左右指针对应的索引的值的和大于 target,右指针的值太大了,right --。
- 若左右指针对应的索引的值的和小于 target,左指针的值太小了,left --。
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left < right) {
int sum = nums[left] + nums[right];
if(sum == target) {
return new int[]{nums[left] , nums[right]};
}else if(sum > target) {
right --;
}else {
left ++;
}
}
return null;
}
}
解题思路:
双指针。
倒序遍历,左指针、右指针分别代表单词的左右下标。
- 倒序遍历,直到指针指向的字符不为空(去除两个单词间有多余的空格),此为单词右下标
- 继续遍历,直到指针指向的字符为空,此为单词左下标
- 将双指针左右夹击的单词字符串加入到 StringBuilder 中
- 最后返回结果,记得去除最后的空格。
代码
class Solution {
public String reverseWords(String s) {
s = s.trim();
StringBuilder sb = new StringBuilder(); // 保存结果
int left = s.length() -1, right = left; // 单词左右
while(left >= 0) {
while(left >= 0 && s.charAt(left) == ' ') {
left --;
}
right = left;
while(left >= 0 && s.charAt(left) != ' ') {
left --;
}
sb.append(s.substring(left + 1, right + 1)).append(" ");
}
return sb.toString().trim();
}
}
第 14 天 搜索与回溯算法 (中等)
解题思路:
深度搜索 + 剪枝
- 返回 false,此路不通:
- 越界
- 当前遍历到的字符不等于 board[i][j] 位置上的字符,匹配不上
不存在以上情况,进行下一步骤。
2. 在上一步的基础上,即匹配成功:
- 当 number 到达 words 数组的最后一个字符,那么说明能够在矩阵 board 中找到一条路径,此时返回 true;
若没有到达结尾,继续匹配字符串的下一个位置的字符
3.递归地去 board[i][j] 的上下左右四个方向去找,重复1、2步骤去判断剩下的路径
遍历过 board[i][j] 的时候,board[i][j] 设置为 ‘\0’,表明已经访问过
在 深度搜索 的过程当中,如果发现某条路已经不通了,就进行回溯操作,board[i][j]重新设置为 words[number],表明未访问过。
代码
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for (int i = 0; i < board.length; ++ i) {
for (int j = 0; j < board[0].length; ++ j ) {
if(dfs(board, i, j, words, 0)) {
return true;
}
}
}
return false;
}
public static boolean dfs(char[][] board, int i, int j, char[] words, int number) {
if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != words[number]) return false;
if (number == words.length - 1) return true;
board[i][j] = '\0';
boolean res = dfs(board, i + 1, j, words, number + 1) || dfs(board, i - 1, j, words, number + 1) || dfs(board, i, j + 1, words, number + 1) || dfs(board, i, j - 1, words, number + 1);
board[i][j] = words[number];
return res;
}
}
代码
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
private int dfs(int i, int j, int m, int n, int k, boolean[][] visited){
// 判断越界,或行坐标和列坐标的数位之和大于k的格子,或者已经访问过了
if(i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || (i / 10 + i % 10 + j / 10 + j % 10) > k) return 0;
visited[i][j] = true;
return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited);
}
}
第 15 天 搜索与回溯算法 (中等)
解题思路:
路径是要从根节点到叶子节点的,不用剪枝。
代码
/**
import java.util.ArrayList;
* 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 {
LinkedList<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> temp = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target);
return result;
}
private void dfs(TreeNode root, int target){
if(root == null) return;
temp.add(root.val);
target -= root.val;
if(target == 0 && root.left == null && root.right == null) result.add(new LinkedList(temp));
dfs(root.left, target);
dfs(root.right, target);
temp.removeLast();
}
}
解题思路:
中序遍历的同时,构建双链表。
有思路,链表实现还是差点。10分钟做不出来,看了题解。。希望再刷能做出来
代码
/*
// 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 {
Node head, pre; // head 头节点, pre 当前结点
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
// 头尾相连
head.left = pre;
pre.right = head;
return head;
}
private void dfs(Node cur) {
if(cur == null) return;
// 递归 “左-中-右” 二叉搜索树的中序遍历结果即为递增序列
dfs(cur.left);
if(pre != null) {
pre.right = cur;
}else {
head = cur;
}
cur.left = pre;
pre = cur;
dfs(cur.right);
}
}
解题思路:
二叉搜索树的基本性质
① 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值;
② 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值;
③ 任意结点的左、右子树也分别为二叉搜索树。
二叉搜索树 中序遍历 为 “左、根、右” 顺序, 得到的是递增序列
反之,我们求遍历顺序 为“左、根、右”,为递减序列,第 k 个即为题目所求。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
static int message;
int count = 0;
public int kthLargest(TreeNode root, int k) {
dfs(root, k);
return message;
}
private void dfs(TreeNode root, int k) {
if(root == null) return;
dfs(root.right, k);
if(k == ++count) {
message = root.val;
return;
}
dfs(root.left, k);
}
}
第 26 天 字符串 (中等)
?> 后面补上
解题思路:
-
开始去除无用的开头空格字符。
-
若函数不能进行有效的转换时,请返回 0:
-
第一个非空格字符不是一个有效整数字符,也不是正或者负号
-
字符串为空或字符串仅包含空白字符
-
-
接着符号位,有可能 ‘’+’’ , ‘’-’’ , 或者无符号 ;这里用 boolean 变量去保存符号位(只有出现"-",flag = false,表明该数位负数,其余符号位情况为正数),返回前判断正负。
-
对符号位后面出现的数字进行遍历拼接,转化为数字,遍历到字符不为数字时,停止;
-
最后,根据 boolean 变量保存的该数字正负情况,确定返回结果 sum 的正负。
拼接公式:sum = sum * 10 + (str.charAt(index) - ‘0’);
对于数字越界处理,在下一位数字进位(*10)之前(Integer.MAX_VALUE:2147483648):
如果 sum 的值已经大于 Integer.MAX_VALUE / 10,那么下一位数字加入, 执行 sum = sum * 10 + (str.charAt(index) - '0');
还没加上下一位数字的值就已经溢出;
如果 sum的值 等于 214748364 (Integer.MAX_VALUE / 10),那么下一位数字加入, 执行 sum = sum * 10 + (str.charAt(index) - '0');
只要 str.charAt(index) - '0'
的值不超过 7
,最后的结果就不会溢出。
代码
class Solution {
public int strToInt(String str) {
if(str == null) {
return 0;
}
int sum = 0, index = 0, len = str.length();
boolean flag = true;
while(index < len && str.charAt(index) == ' ') {
index ++;
}
if(index == len) return 0;
if(str.charAt(index) == '+' || str.charAt(index) == '-') {
flag = str.charAt(index) == '-' ? false : true;
index ++;
}
while (index < len) {
if(str.charAt(index) < '0' || str.charAt(index) > '9') break;
if(sum > Integer.MAX_VALUE / 10 || (sum == Integer.MAX_VALUE / 10 && str.charAt(index) > '7')){
return flag ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
sum = sum * 10 + (str.charAt(index) - '0');
index ++;
}
return flag?sum:-sum;
}
}