概述
剑指OFFER 二刷
分类:桶排序
题号:【46】
Day1:栈与队列(简单)
【09】用两个栈实现队列
剑指 Offer 09. 用两个栈实现队列
// 栈本质上就是队列,遇到栈通通用Deque来
class CQueue {
Deque<Integer> A, B;
public CQueue() {
A = new LinkedList<Integer>(); // 主队列
B = new LinkedList<Integer>(); // 辅助队列
}
public void appendTail(int value) {
A.addLast(value);
}
public int deleteHead() {
if(!B.isEmpty()) return B.removeLast();
if(A.isEmpty()) return -1;
while(!A.isEmpty())
B.addLast(A.removeLast());
return B.removeLast();
}
}
【30】包含min函数的栈
剑指 Offer 30. 包含min函数的栈
class MinStack {
Deque<Integer> stack;
Deque<Integer> help;
/** initialize your data structure here. */
public MinStack() {
stack = new LinkedList<>();
help = new LinkedList<>();
}
// public void push(int x) {
// stack.addLast(x);
// if(help.isEmpty()) {
// help.addLast(x);
// } else {
// int tmp = help.peekLast();
// if(x <= tmp) {
// help.addLast(x);
// } else {
// help.addLast(tmp);
// }
// }
// }
// 对上面逻辑的整合
public void push(int x) {
stack.addLast(x);
if(help.isEmpty() || x <= help.peekLast()) {
help.addLast(x);
} else {
help.addLast(help.peekLast());
}
}
public void pop() {
stack.removeLast();
help.removeLast();
}
public int top() {
return stack.peekLast();
}
public int min() {
return help.peekLast();
}
}
Day2:链表(简单)
【06】从尾到头打印链表
剑指 Offer 06. 从尾到头打印链表
// 1. 递归法
class Solution {
List<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
if(head == null) return new int[]{};
search(head,list);
int[] arr = new int[list.size()];
for(int i = 0; i < list.size(); i++){
arr[i] = list.get(i);
}
return arr;
}
public void search(ListNode cur, List<Integer> List) {
if(cur == null) return;
// 开始下一次
search(cur.next,list);
// 回到本体
list.add(cur.val);
}
}
// 2. 栈
class Solution {
public int[] reversePrint(ListNode head) {
Deque<Integer> stack = new LinkedList<>();
while(head != null) {
stack.addLast(head.val);
head = head.next;
}
int[] arr = new int[stack.size()];
for(int i = 0; i < arr.length; i++) {
arr[i] = stack.removeLast();
}
return arr;
}
}
【24】反转链表
剑指 Offer 24. 反转链表
class Solution {
ListNode newHead;
public ListNode reverseList(ListNode head) {
if(head == null) return null;
reverse(head);
return newHead;
}
public ListNode reverse(ListNode head) {
if(head.next == null) {
newHead = head;
return head;
}
ListNode nextNode = head.next;
ListNode newPre = reverse(nextNode);
newPre.next = head;
head.next = null;
return head;
}
}
【35】复杂链表的复制
剑指 Offer 35. 复杂链表的复制
/*
// 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;
}
}
*/
// 方法一:构建A-A'-B-B'-C-C'
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
// 1. 构建A-A'-B-B'-C-C',初始化新节点的val
Node p = head;
while(p != null) {
Node node = new Node(p.val);
node.next = p.next;
p.next = node;
p = node.next;
}
// 2. 构建random
p = head;
while(p != null) {
if(p.random != null){
p.next.random = p.random.next;
}
p = p.next.next;
}
// 3. 拆分,构建next
p = head.next;
Node pre = head;
Node res = head.next;
while(p.next!=null) {
pre.next = pre.next.next;
p.next = p.next.next;
pre=pre.next;
p=p.next;
}
pre.next = null;
return res;
}
}
// 方法二:HashMap先存好新节点的地址。
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> map = new HashMap<>();
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != null) {
// map.get(cur) 是我们 复制完的链表中的Node结构
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 5. 返回新链表的头节点
return map.get(head);
}
}
Day3:字符串(简单)
【05】替换空格
剑指 Offer 05. 替换空格
class Solution {
public String replaceSpace(String s) {
// 如果是连续的空格“ ”,split后的长度也是0
int len = s.length();
StringBuffer ans = new StringBuffer();
// 1. 处理每一个空格
for(int i = 0; i < len; i++) {
if(s.charAt(i) == ' ') {
ans.append('%');
ans.append('2');
ans.append('0');
} else {
ans.append(s.charAt(i));
}
}
return new String(ans);
}
}
【58】左旋转字符串
class Solution {
public String reverseLeftWords(String s, int n) {
// 1. 原字符串分成2个部分
// 子串part1:索引[n~length-1];
// 子串part2: 索引[0~n-1]
StringBuffer ans = new StringBuffer();
ans.append(s.substring(n,s.length()));
ans.append(s.substring(0,n)); //[0~n-1], substring()是左开右闭的,因此是substring(0,n)
return ans.toString();
}
}
Day4:查找算法(简单)
【03】数组中重复的数字
剑指 Offer 03. 数组中重复的数字
Method1:归位法==【优解】==
// 时间复杂度:O(n); 空间复杂度:O(1);原数组:修改
/**
题目条件:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内
一个数 X=nums[i] 一定有一个索引位置 X 是 nums[i] 对应的
*/
class Solution {
public int findRepeatNumber(int[] nums) {
int i = 0;
while(i < nums.length) {
if(nums[i] == i) {
i++;
continue;
}
// 一直换索引i,直到换到一个该索引应该存在的值
if(nums[nums[i]] == nums[i]) return nums[i];
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
return -1;
}
}
Method2:二分搜索+遍历【看下剑指OFFER原书】
此方法跟本题题目条件有些不一样
// 时间复杂度O(n), 空间复杂度O(1), 原数组:修改
class Solution {
//方法5:对0到n-1进行二分查找,时间O(nlogn),空间O(1),不修改原数据,用时间换空间
//该方法需要数字一定有重复的才行,因此如果题目修改在长度为n,数字在1到n-1的情况下,此时数组中至少有一个数字是重复的,该方法可以通过。
public int findRepeatNumber(int[] nums) {
//统计nums中元素位于0到m的数量,如果数量大于这个值,那么重复的元素肯定是位于0到m的
int min = 0 ;
int max = nums.length-1;
while(min<max){
int mid = (max+min)>>1;
int count = countRange(nums,min,mid);
if(count > mid-min+1) {
max = mid;
}else{
min = mid+1;
}
}
//最后min=max
return min;
}
//统计范围内元素数量,时间O(n)
private int countRange(int[] nums,int min,int max){
int count = 0 ;
for(int num:nums){
if(num>=min && num<=max){
count++;
}
}
return count;
}
}
Method3:哈希表+遍历
// 时间复杂度:O(n); 空间复杂度:O(n);原数组:不修改
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> dic = new HashSet<>();
for(int num : nums) {
if(dic.contains(num)) return num;
dic.add(num);
}
return -1;
}
}
Method4:排序+指针遍历
// 时间:O(nlogn);空间:O(1):原数组:修改
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
int slow = 0;
int fast = 1;
while(fast != nums.length){
if(nums[slow] == nums[fast]){
return nums[slow];
}
slow++;
fast++;
}
return -1;
}
}
【53-I】在排序数组中查找数字 I
剑指 Offer 53 - I. 在排序数组中查找数字 I
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0) return 0;
// 二分查找,<=7 的范围, return r
int l = -1;
int r = nums.length;
while(l+1!=r) {
int mid = (l+r) >> 1;
if(nums[mid] <= target-1) {
l = mid;
} else {
r = mid;
}
}
// 特殊边界值处理:因为最终用的是指针r,故处理r就好。
// r一直没动在右边 或 r停下来不是target,即原来就没有target
// 直接return 0
// 情况1.没有target
if(r == nums.length || nums[r] != target) return 0;
// 情况2. 有target
int ans = 0;
while(r < nums.length) {
if(nums[r] == target){
ans++;
}
r++;
}
return ans;
}
}
【53-II】在排序数组中查找数字 II
剑指 Offer 53 - II. 0~n-1中缺失的数字
class Solution {
public int missingNumber(int[] nums) {
// 合法情况:nums[mid] == mid
// 不合法情况:nums[mid] != mid
int l = -1;
int r = nums.length;
while(l+1!=r) {
int mid = (l + r) >> 1;
if(nums[mid] == mid) {
l = mid;
} else {
r = mid;
}
}
return r;
}
}
Day5:查找算法(中等)
【04】二维数组中查找
剑指 Offer 04. 二维数组中的查找
// 线性查找:从左下角or右上角开始
// 特点:搜索方向只有两个:direction 1:小于自己 ; direction 2:大于自己; 等于就直接返回
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int rows = matrix.length, columns = matrix[0].length;
// 从右上角出发。
int row = 0, column = columns - 1;
while (row < rows && column >= 0) {
int num = matrix[row][column];
if (num == target) {
return true;
} else if (num > target) {
column--;
} else {
row++;
}
}
return false;
}
}
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
int row = matrix.length;
int col = matrix[0].length;
if(matrix[0][0] > target) return false;
// 1. 二分查找[0][j]; [i][0]
// 1.1目标:<= target - 1; 返回:l
int l = -1;
int r = col;
while(l+1 != r) {
int mid = (l + r) >> 1;
if(matrix[0][mid] <= target) {
l = mid;
} else {
r = mid;
}
}
// return l, l 一定有
if(matrix[0][l] == target) return true;
// 记录最终搜索的最大 列索引
int n = l;
l = -1;
r = row;
while(l+1 != r) {
int mid = (l + r) >> 1;
if(matrix[mid][0] <= target) {
l = mid;
} else {
r = mid;
}
}
// return l
if(matrix[l][0] == target) return true;
// 记录最终搜索的最大 行索引
int m = l;
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= n; j++) {
if(matrix[i][j] == target) {
return true;
}
}
}
return false;
}
}
【11】旋转数组的最小数字
剑指 Offer 11. 旋转数组的最小数字
// O(n)
class Solution {
public int minArray(int[] numbers) {
if(numbers.length == 1) return numbers[0];
int slow = 0, fast = 1;
while(fast < numbers.length) {
if( numbers[slow] > numbers[fast]) {
return numbers[fast];
}
slow++;
fast++;
}
return numbers[0];
}
}
【50】第一个只出现一次的字符
剑指 Offer 50. 第一个只出现一次的字符
class Solution {
// false:出现多次;true:仅出现一次
Map<Character,Boolean> map = new HashMap<>();
public char firstUniqChar(String s) {
if(s.length() == 0) return ' ';
if(s.length() == 1) return s.charAt(0);
for(int i = 0; i < s.length(); i++) {
char tmp = s.charAt(i);
// if(!map.containsKey(tmp)) {
// map.put(tmp, true);
// } else {
// map.put(tmp, false);
// }
map.put(tmp,!map.containsKey(tmp));
}
for(int i = 0; i < s.length(); i++) {
if(map.get(s.charAt(i)) == true) return s.charAt(i);
}
return ' ';
}
}
Day6:搜索与回溯算法(简单)
【32】从上到下打印二叉树
剑指 Offer 32 - I. 从上到下打印二叉树
/**
* 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[]{};
ArrayList<TreeNode> list = new ArrayList<>();
Deque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
while(!queue.isEmpty()) {
int size = queue.size();
for(int i = 0; i < size; i++) {
TreeNode node = queue.removeFirst();
list.add(node);
if(node.left != null) queue.addLast(node.left);
if(node.right != null) queue.addLast(node.right);
}
}
int[] ans = new int[list.size()];
for(int i = 0; i < ans.length; i++) {
// 取出节点,放入节点的值val
ans[i] = list.get(i).val;
}
return ans;
}
}
【32-II】从上到下打印二叉树 II
剑指 Offer 32 - II. 从上到下打印二叉树 II
/**
* 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) {
List<List<Integer>> ans = new ArrayList<>();
if(root == null) return ans;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
while(!queue.isEmpty()) {
int size = queue.size();
List<Integer> list = new ArrayList<>();
for(int i = 0; i < size; i++) {
TreeNode node = queue.removeFirst();
list.add(node.val);
if(node.left != null) queue.addLast(node.left);
if(node.right != null) queue.addLast(node.right);
}
ans.add(new ArrayList(list));
}
return ans;
}
}
【32-III】从上到下打印二叉树 III
剑指 Offer 32 - III. 从上到下打印二叉树 III
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if(root == null) return ans;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.addLast(root);
boolean flag = false;
while(!queue.isEmpty()) {
int size = queue.size();
Deque<Integer> list = new ArrayDeque<>();
for(int i = 0; i < size; i++) {
TreeNode node = queue.removeFirst();
// 关键点:改变入队列的方式,
if(flag){ // 从头
list.addFirst(node.val);
} else { // 从尾
list.addLast(node.val);
}
if(node.left != null) queue.addLast(node.left);
if(node.right != null) queue.addLast(node.right);
}
flag = !flag;
ans.add(new ArrayList(list));
}
return ans;
}
}
Day7:搜索与回溯算法(简单)
【26】数的子结构
/**
* 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 || B==null) return false;
// A中节点对上了,才开始移动 节点B
if(A.val == B.val && help(A,B)) return true;
return isSubStructure(A.left, B) || isSubStructure(A.right,B);
}
public boolean help(TreeNode n1, TreeNode n2) {
if(n1 ==null && n2 == null) return true; // n1、n2 同时为 null
if(n2 == null) return true; // n2 为null,表示先遍历完了
if(n1 == null) return false; // n2 不为null,n1 为null
if(n1.val != n2.val) return false;
return help(n1.left,n2.left) && help(n1.right,n2.right);
}
}
class Solution {
//先序遍历树 A 中的每个节点 n_A
public boolean isSubStructure(TreeNode A, TreeNode B) {
if (B == null || A == null) {
return false;
}
//若,当前两个“根结点”匹配成功
if (A.val == B.val && (recur(A.left, B.left) && recur(A.right, B.right))) {
return true;
}
//此时,当前两个“根结点”没匹配成功,则继续遍历后续结点
//往左右两边的子树再开始寻找
return isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
//判断树 A 中 以 n_A 为根节点的子树 是否包含树 B
private boolean recur(TreeNode root1, TreeNode root2) {
//roo2 == null, 说明搜索roo2所在的子树的某一边已经被遍历完了
if (root2 == null) {
return true;
}
if (root1 == null) {
return false;
}
if (root1.val == root2.val) {
return recur(root1.left, root2.left) && recur(root1.right, root2.right);
} else {
return false;
}
}
}
【27】二叉树的镜像
剑指 Offer 27. 二叉树的镜像
/**
* 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;
help(root);
return root;
}
public void help(TreeNode node) {
// base-case:叶节点
if(node == null) return;
//
if(node.left != null) {
help(node.left);
}
//
TreeNode tmp = node.left;
//
if(node.right != null) {
help(node.right);
}
// 回到本体,改变结构
node.left = node.right;
node.right = tmp;
}
}
【28】对称的二叉树
剑指 Offer 28. 对称的二叉树
/*做递归思考三步:
递归的函数要干什么?
函数的作用是判断传入的两个树是否镜像。
输入:TreeNode left, TreeNode right
输出:是:true,不是:false
递归停止的条件是什么?
左节点和右节点都为空 -> 倒底了都长得一样 ->true
左节点为空的时候右节点不为空,或反之 -> 长得不一样-> false
左右节点值不相等 -> 长得不一样 -> false
从某层到下一层的关系是什么?
要想两棵树镜像,那么一棵树左边的左边要和二棵树右边的右边镜像,一棵树左边的右边要和二棵树右边的左边镜像
调用递归函数传入左左和右右
调用递归函数传入左右和右左
只有左左和右右镜像且左右和右左镜像的时候,我们才能说这两棵树是镜像的
调用递归函数,我们想知道它的左右孩子是否镜像,传入的值是root的左孩子和右孩子。这之前记得判个root==null。
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
// 终点判断
if(L == null && R == null) return true;
// 中间过程判断;① 树长得层次不一样;② 节点值不一样
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
Day8:动态规划(简单)
【10-I】斐波那契数列
剑指 Offer 10- I. 斐波那契数列
class Solution {
static final int MOD = 1000000007;
public int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
int count = 2;
int pre = 0;
int cur = 1;
int sum = 0;
while(count <= n) { // 或者写成for(int i =2;i<=n;i++){}
sum = (pre+cur)%MOD;
pre = cur;
cur = sum;
count++;
}
return sum;
}
}
// 一种数学上的方法
class Solution {
static final int MOD = 1000000007;
public int fib(int n) {
if (n < 2) {
return n;
}
int[][] q = {{1, 1}, {1, 0}};
int[][] res = pow(q, n - 1);
return res[0][0];
}
public int[][] pow(int[][] a, int n) {
int[][] ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a); // 保留当前参与平方操作的,底数的内容
}
n >>= 1; // n /= 2;
a = multiply(a, a);
}
return ret;
}
public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = (int) (((long) a[i][0] * b[0][j] + (long) a[i][1] * b[1][j]) % MOD);
}
}
return c;
}
}
【10-II】青蛙跳台阶问题
剑指 Offer 10- II. 青蛙跳台阶问题
class Solution {
static final int MOD = 1000000007;
public int numWays(int n) {
if(n==0) return 1;
if(n==1) return 1;
// n 从2开始
// 初始化
int pre = 1; // f(0)
int cur = 1; // f(1)
int sum = 0;
for(int i = 2; i <= n; i++){
sum = (pre+cur)%MOD;
pre = cur;
cur = sum;
}
return sum;
}
}
【63】股票的最大利润
剑指 Offer 63. 股票的最大利润
class Solution {
public int maxProfit(int[] prices) {
if(prices.length == 0) return 0;
// 1. 记录局部最小买入价,初始化为prices[0]
int localMin = prices[0]; // 更新时机:当前买入价 < 局部最小买入价,更新。
// 2. 记录最大利润
int maxProfit = 0; // 更新时机:当前买入价 > 局部最小买入价,可以交易。
for(int i = 1; i<prices.length;i++) {
// if(prices[i] < localMin) {
// localMin = prices[i];
// continue;
// }
// 等效
localMin = Math.min(localMin,prices[i]);
maxProfit = Math.max(maxProfit, prices[i] - localMin);
}
return maxProfit;
}
}
Day9:动态规划(中等)
【42】连续子数组的最大和
剑指 Offer 42. 连续子数组的最大和
class Solution {
public int maxSubArray(int[] nums) {
/**
维护变量:subSum,维护子数组的和
连续子数组 起始位置:start,初始化为 0
更新时机:if subSum < 0,那么更新start为当前nums[i]
同时更新subSum为nums[i]
连续子数组 结束位置:i
*/
// base-case
if(nums.length == 0) return 0;
int finalSum = nums[0];
int subSum = nums[0];
for(int i = 1; i < nums.length; i++) {
// if(subSum < 0) {
// subSum = nums[i];
// finalSum = Math.max(subSum,finalSum);
// continue;
// }
// subSum += nums[i];
// finalSum = Math.max(subSum,finalSum);
// 优化上述代码
subSum = Math.max(subSum, 0);
subSum += nums[i];
finalSum = Math.max(subSum,finalSum);
}
return finalSum;
}
}
【47】礼物的最大价值
剑指 Offer 47. 礼物的最大价值
class Solution {
public int maxValue(int[][] grid) {
if(grid.length == 0) return 0;
int rows = grid.length;
int cols = grid[0].length;
// int ans = grid[0][0];
// 直接修改原数组,节省空间复杂度
// // 1. 初始化第一行
// for(int j = 1; j < cols; j++) {
// grid[0][j] += grid[0][j-1];
// }
// // 2. 初始化第一列
// for(int i = 1; i < rows; i++) {
// grid[i][0] += grid[i-1][0];
// }
// 特殊情况:只有一行或者一列
// if(rows == 1) return grid[0][cols-1];
// if(cols == 1) return grid[rows-1][0];
// 3. 从 grid[1][1] 开始走 i=1;j=1 开始
// 优化了上述代码,把for循环写到了一起
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(i==0&&j==0) continue;
if(i==0){
grid[i][j] += grid[i][j-1];
} else if (j==0) {
grid[i][j] += grid[i-1][j];
} else {
grid[i][j] += Math.max(grid[i-1][j],grid[i][j-1]);
}
}
}
return grid[rows-1][cols-1];
}
}
Day10:动态规划(中等)
【46】把数字翻译成字符串
剑指 Offer 46. 把数字翻译成字符串
递归法1:需要转成字符串操作
-
String.valueOf();
-
substring();
-
compareTo(“数字”);
class Solution {
public int translateNum(int num) {
//将字符串转化为数字
String str = String.valueOf(num);
//dfs遍历字符串求解
return dfs(str, 0);
}
//表示从index位置开始有好多种翻译方法
public int dfs(String str, int index){
// base-case: 倒数第二个 走一步到 倒数第一个 或者 倒数第二个走两步走到外边儿了
if(index == str.length() - 1 || index ==str.length())
return 1;
int res = dfs(str, index + 1);
//以当前字符的下标为开始,截取两位,判断这位组成的数字是否在10~25之间,防止“06”这样的两位数
//如果在这一次就可以直接翻译两个字符,然后从两个字符后面的位置开始翻译
String temp = str.substring(index, index + 2);
if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0)
res += dfs(str, index + 2);
return res;
}
}
递归法2:直接对int进行操作
// 方法2:不用转成字符串
class Solution {
public int translateNum(int num) {
if (num < 10) {
return 1;
}
int re = num % 100; // 天然处理了12208 后面 08 这样的子部分;而在字符串中则需要递归法1的compareTo实现
if (re < 10 || re >25) { // 不能合成2个单个数字
return translateNum(num / 10);
} else { // 10<= 且 <=25 之间,可以合成 2个 单个数字
return translateNum(num / 10) + translateNum(num / 100);
}
}
}
动态规划法
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int[] dp = new int[s.length()+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= s.length(); i ++){
String temp = s.substring(i-2, i);
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[s.length()];
}
}
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int pre = 1;
int cur = 1;
int sum;
for(int i = 2; i <= s.length(); i ++){
String temp = s.substring(i-2, i);
if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0) {
sum = pre + cur;
pre = cur;
cur = sum;
} else {
pre = cur;
cur = pre;
}
}
return cur;
}
}
【48】最长不含重复字符的子字符串
本题需要考虑的特殊样例:
“abba"
“dvdfq”
剑指 Offer 48. 最长不含重复字符的子字符串
// 优化后
// space: O(1);字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用 O(128) = O(1)大小的额外空间。
// time: O(n)
class Solution {
public int lengthOfLongestSubstring(String s) {
/**
HashSet:维护当前子串
更新条件:查询hashset,contains当前元素
更新操作:① 清空hashset;② put 当前元素
maxLen:维护 全局最大 长度
更新条件:hashset被清空的时候
更新操作:Math.max(hashset.size(), maxLen)
subLen:子串的长度,
*/
if(s == null) return 0;
if(s.length() == 0) return 0;
int len = s.length();
int maxLen = 0;
int subStart = 0;
HashMap<Character,Integer> map = new HashMap<>();
for(int i = 0; i < len;i++) {
char tmp = s.charAt(i);
if(map.getOrDefault(tmp, len) < i) { // 不存在返回len(任意一个不存在的索引),是为了解决当hashmap中没有当前元素的情况。
// 更新左边界还要求 比一直记录的subStart要大于等于,不然就不是记录当前真实子串了
if(map.get(tmp) >= subStart) { // 等于是为了第一次subStart=0的时候也能正常执行。
subStart = map.get(tmp) + 1;
}
}
map.put(tmp, i);
maxLen = Math.max(maxLen, i-subStart+1);
}
return maxLen;
}
}
// 毫无优化
class Solution {
public int lengthOfLongestSubstring(String s) {
/**
HashSet:维护当前子串
更新条件:查询hashset,contains当前元素
更新操作:① 清空hashset;② put 当前元素
maxLen:维护 全局最大 长度
更新条件:hashset被清空的时候
更新操作:Math.max(hashset.size(), maxLen)
subLen:子串的长度,
pre:记录重复元素上一次出现的位置
*/
if(s == null) return 0;
if(s.length() == 0) return 0;
int maxLen = 0;
int subLen = 0;
HashMap<Character,Integer> map = new HashMap<>();
for(int i = 0; i < s.length();) {
char tmp = s.charAt(i);
if(map.containsKey(tmp)) {
int lastPostion = map.get(tmp);
map.clear();
subLen = 0;
i = lastPostion + 1;
} else {
map.put(tmp,i);
subLen++;
maxLen = Math.max(subLen, maxLen);
// 循环步长控制
i++;
}
}
return maxLen;
}
}
Day11:双指针(简单)
【18】删除链表的节点
剑指 Offer 18. 删除链表的节点
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head == null) return null;
if(head.val == val) return head.next;
ListNode p = head;
while(p.next != null) {
if(p.next.val == val) {
p.next = p.next.next;
break;
}
p = p.next;
}
return head;
}
}
【22】链表中倒数第k个节点
Method1:双指针(优解)
// 双指针,两个指针【间隔 k 步】,当【快指针】到null时,慢指针刚好指到【倒数第k个节点】
// space:O(1)
// time:O(n)
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head, slow = head;
for(int i = 1; i <= k; i++) {
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
Method:利用栈
// 用一个 栈 倒出来
// space:O(n)
// time:O(n)
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
Deque<ListNode> stack = new ArrayDeque<>();
ListNode p = head;
while(p != null) {
stack.addLast(p);
p = p.next;
}
for(int i = 1; i <= k; i++) {
p = stack.removeLast();
}
return p;
}
}
Day 12:双指针(简单)
【25】合并两个排序的链表
剑指 Offer 25. 合并两个排序的链表
/**
* 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 && l2 == null) return null;
if(l1 == null && l2 != null) return l2;
if(l1 != null && l2 == null) return l1;
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
// 1. 循环结束时:其中一个遍历完了
while(l1 != null && l2 != null) {
if(l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
// 2. 串联剩下的,不用遍历的。
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
【52】两个链表的第一个公共节点
剑指 Offer 52. 两个链表的第一个公共节点
Method 1:优解,非常之漂亮
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
Method 2:常规想法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
int lenA = 0, lenB = 0;
ListNode pA = headA;
ListNode pB = headB;
// 1. 计算长度
while(pA != null) { pA = pA.next; lenA++;}
while(pB != null) { pB = pB.next; lenB++;}
// 2. 调整到长度一致的位置
int diff = Math.abs(lenA - lenB);
if(lenA < lenB) {
for(int i = 0; i < diff; i++) {
headB = headB.next;
}
} else {
for(int i = 0; i < diff; i++) {
headA = headA.next;
}
}
// 3. 开始寻找
while(headA != null) {
if(headA == headB) return headA;
headA = headA.next;
headB = headB.next;
}
return null;
}
}
Day 13:双指针(简单)
【21】调整数组顺序使奇数位于偶数前面
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
class Solution {
public int[] exchange(int[] nums) {
int l = 0, r = nums.length; // r指向偶数区域
while(l < r) {
// l 遇到奇数 l++;l 遇到偶数,跟(r--)进行交换,l不++
if(nums[l] % 2 == 0) {
swap(nums,l,--r);
} else {
l++;
}
}
return nums;
}
public static void swap(int[] nums, int l, int r) {
if(nums == null) return;
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
}
}
【57】和为s的两个数字
剑指 Offer 57. 和为s的两个数字
class Solution {
public int[] twoSum(int[] nums, int target) {
int i = 0, j = nums.length - 1;
while(i < j) {
int s = nums[i] + nums[j];
if(s < target) i++;
else if(s > target) j--;
else return new int[] { nums[i], nums[j] };
}
return new int[0];
}
}
区分另一道题
1. 两数之和
要求返回的是【元素索引】,因为原数组不是有序排列的,因此不能sort,sort之后就改变了索引的对应关系。
Method1:哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i< nums.length; i++) {
if(map.containsKey(target - nums[i])) {
return new int[] {map.get(target-nums[i]),i};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
【58-I】翻转单词顺序
剑指 Offer 58 - I. 翻转单词顺序
class Solution {
public String reverseWords(String s) {
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
// 问题 //if(strs[i] == "") continue;
if(strs[i].equals("")) continue; // 遇到空单词则跳过
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}
}
一个严肃的问题
if(strs[i] == "") continue;
== 用于基础类型判断值;
此时,右侧""
是一个字面量,左侧strs[i]拿到的是一个对象,String 类;不是基础数据类型。
这里 == 比较的是 对象地址
Day 14:搜索与回溯算法(中等)
【12】矩阵中的路径
剑指 Offer 12. 矩阵中的路径
class Solution {
int rows;
int cols;
public boolean exist(char[][] board, String word) {
rows = board.length;
cols = board[0].length;
boolean[][] vis = new boolean[rows][cols];
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(dfs(board,word,vis,0,i,j)) return true;
}
}
return false;
}
public boolean dfs(char[][] board, String word, boolean[][] vis, int targetIndex, int i, int j) {
// base-case
if(targetIndex == word.length()) return true;
// 越界
if(i<0 || j<0 || i > rows-1 || j > cols-1 || vis[i][j]) return false;
// 目标不符合
if(board[i][j] != word.charAt(targetIndex)) return false;
boolean res = false;
// 合法条件,接下来是符合board[i][j] == word.charAt(targetIndex)
vis[i][j] = true;
res = dfs(board,word,vis,targetIndex+1,i+1,j) || dfs(board,word,vis,targetIndex+1,i-1,j) || dfs(board,word,vis,targetIndex+1,i,j+1) || dfs(board,word,vis,targetIndex+1,i,j-1);
vis[i][j] = false;
return res;
}
}
优化上面的代码
思路:不建立 boolean 二维数组 visited,直接再board原数组上进行修改
区别1:做选择时,用’ ’修改board;撤销选择时,用 当前寻找的字符替代回去。
区别2:原来判断非法要比较vis【i】【j】是不是true(即,被选择过的)
现在if(board[i][j] != word.charAt(targetIndex)) return false;
不仅实现了比较是不是要的开头,而且比较了是不是被选择过的
class Solution {
int rows;
int cols;
public boolean exist(char[][] board, String word) {
rows = board.length;
cols = board[0].length;
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(dfs(board,word,0,i,j)) return true;
}
}
return false;
}
public boolean dfs(char[][] board, String word, int targetIndex, int i, int j) {
// base-case
if(targetIndex == word.length()) return true;
// 越界
if(i<0 || j<0 || i > rows-1 || j > cols-1) return false;
if(board[i][j] != word.charAt(targetIndex)) return false;
// 合法条件,接下来是符合board[i][j] == word.charAt(targetIndex)
board[i][j] = '