概述
剑指offer-leetcode(Java)
- 第1天 栈与队列(简单)
- 剑指 Offer 09. 用两个栈实现队列
- 剑指 Offer 30. 包含min函数的栈(没思路)
- 第 2 天 链表(简单)
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 24. 反转链表
- [M] 剑指 Offer 35. 复杂链表的复制
- 第 3 天 字符串(简单)
- 剑指 Offer 05. 替换空格
- 剑指 Offer 58 - II. 左旋转字符串
- 第 4 天 查找算法(简单)
- 剑指 Offer 03. 数组中重复的数字
- 剑指 Offer 53 - I. 在排序数组中查找数字 I
- 剑指 Offer 53 - II. 0~n-1中缺失的数字
- 第 5 天 查找算法(中等)
- [M] 剑指 Offer 04. 二维数组中的查找
- [不会] 剑指 Offer 11. 旋转数组的最小数字
- 剑指 Offer 50. 第一个只出现一次的字符
- 第 6 天 搜索与回溯算法(简单)(广搜)
- 剑指 Offer 32 - I. 从上到下打印二叉树
- [注意泛型使用] 剑指 Offer 32 - II. 从上到下打印二叉树 II
- [M] 剑指 Offer 32 - III. 从上到下打印二叉树 III
- 第 7 天 搜索与回溯算法(简单)(深搜)
- [M] 剑指 Offer 26. 树的子结构
- 剑指 Offer 27. 二叉树的镜像
- [没想到] 剑指 Offer 28. 对称的二叉树
- 第 8 天 动态规划(简单)
- 剑指 Offer 10- I. 斐波那契数列
- 剑指 Offer 10- II. 青蛙跳台阶问题
- [M] 剑指 Offer 63. 股票的最大利润
- [E] 121. 买卖股票的最佳时机(同上)
- [M] 122. 买卖股票的最佳时机 II
- [H] 123. 买卖股票的最佳时机 III
- [H] 188. 买卖股票的最佳时机 IV
- [M] 309. 最佳买卖股票时机含冷冻期
- [M] 714. 买卖股票的最佳时机含手续费
- 第 9 天 动态规划(中等)
- 剑指 Offer 42. 连续子数组的最大和
- 剑指 Offer 47. 礼物的最大价值
- 第 10 天 动态规划(中等)
- [M] 剑指 Offer 46. 把数字翻译成字符串
- [M] 剑指 Offer 48. 最长不含重复字符的子字符串
- 第 11 天 双指针(简单)
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 第 12 天 双指针(简单)
- [不会] 剑指 Offer 25. 合并两个排序的链表
- 剑指 Offer 52. 两个链表的第一个公共节点
- 第 13 天 双指针(简单)
- 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
- 剑指 Offer 57. 和为s的两个数字
- 剑指 Offer 58 - I. 翻转单词顺序
- 第 14 天 搜索与回溯算法(中等)
- [M] 剑指 Offer 12. 矩阵中的路径
- [M]剑指 Offer 13. 机器人的运动范围
- 第 15 天 搜索与回溯算法(中等)
- [M] 剑指 Offer 34. 二叉树中和为某一值的路径
- [不会][M] 剑指 Offer 36. 二叉搜索树与双向链表
- 剑指 Offer 54. 二叉搜索树的第k大节点
第1天 栈与队列(简单)
剑指 Offer 09. 用两个栈实现队列
剑指 Offer 09. 用两个栈实现队列
方法一:一个栈从底部是头,另一个栈底部是尾,要添加时把所有信息放到第一个然后加,要删除时把所有信息移到第二个里再删。
class CQueue {
//bottom is head
private Stack<Integer> s1;
//bottom is tail
private Stack<Integer> s2;
// current stack
private Stack<Integer> cs;
public CQueue() {
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
cs = s2;
}
public void appendTail(int value) {
if (cs != s1) {
change(s2,s1);
cs = s1;
}
cs.push(value);
}
public int deleteHead() {
if (cs != s2) {
change(s1,s2);
cs = s2;
} else {
// 之前cs是s2表明刚添加过,则队列不可能是空
if (cs.isEmpty()) {
return -1;
}
}
return cs.pop();
}
private void change(Stack<Integer> from, Stack<Integer> to) {
while(!from.isEmpty()){
Integer a = from.pop();
to.push(a);
}
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
方法二:方法一移动操作占用大量时间,题目里只需要添加和删除,所以没有必要把所有信息存一个栈。第一个栈存添加的,第二个栈存删除的。栈是先进先出,而队列是先进后出。当添加时直接加第一个里,删除时先看第二个里有没有,若没有则把所有元素移到第二个再删,如果有则说明上次移动过去的还没删完,直接删。
class CQueue {
//add stack
private Stack<Integer> s1;
//delete stack
private Stack<Integer> s2;
public CQueue() {
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
}
public void appendTail(int value) {
s1.push(value);
}
public int deleteHead() {
if (s2.isEmpty()) {
if (s1.isEmpty()) {
return -1;
} else {
while(!s1.isEmpty()){
s2.push(s1.pop());
}
return s2.pop();
}
} else {
return s2.pop();
}
}
}
剑指 Offer 30. 包含min函数的栈(没思路)
剑指 Offer 30. 包含min函数的栈
重点是辅助栈使min方法O(1)。非严格降序是大于等于。
class MinStack {
//main stack
Stack<Integer> ms;
//assist stack
Stack<Integer> as;
/** initialize your data structure here. */
public MinStack() {
ms = new Stack<Integer>();
as = new Stack<Integer>();
}
public void push(int x) {
ms.push(x);
if (as.isEmpty() || as.peek()>=x) {
as.push(x);
}
}
public void pop() {
if(!ms.isEmpty()){
int a = ms.pop();
if (a == as.peek()) {
as.pop();
}
}
}
public int top() {
return ms.peek();
}
public int min() {
return as.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 天 链表(简单)
剑指 Offer 06. 从尾到头打印链表
剑指 Offer 06. 从尾到头打印链表
选择反转链表后直接输出,也可以用栈来辅助。
/**
* 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 count = 0;
ListNode a = head;
while (a != null) {
count += 1;
a = a.next;
}
head = reverse(head);
int[] ans = new int[count];
for (int i=0; i<count; i++) {
ans[i] = head.val;
head = head.next;
}
return ans;
}
private ListNode reverse(ListNode root) {
if (root == null) return null;
ListNode a=root,b,c;
b = a.next;
if (b == null) return a;
c = b.next;
a.next = null;
while (c != null) {
b.next = a;
a = b; b = c; c = b.next;
}
b.next = a;
return b;
}
}
剑指 Offer 24. 反转链表
剑指 Offer 24. 反转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode root) {
if (root == null) return null;
ListNode a=root,b,c;
b = a.next;
if (b == null) return a;
c = b.next;
a.next = null;
while (c != null) {
b.next = a;
a = b; b = c; c = b.next;
}
b.next = a;
return b;
}
}
[M] 剑指 Offer 35. 复杂链表的复制
剑指 Offer 35. 复杂链表的复制
方法一:遍历两边,第一次是正常的顺序,同时用哈希表记录两次对应的地址,第二遍赋值random指针。时间O(n),空间O(n)。
/*
// 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;
HashMap<Node,Node> cor = new HashMap<Node,Node>();
Node newHead = new Node(0);
Node newCur = newHead, cur = head;
while (cur != null) {
newCur.val = cur.val;
cor.put(cur, newCur);
cur = cur.next;
if(cur == null) {
break;
}
newCur.next = new Node(0);
newCur = newCur.next;
}
cur = head;
Node ran,from,to;
while (cur != null) {
ran = cur.random;
from = cor.get(cur);
to = cor.get(ran);
from.random = to;
cur = cur.next;
}
return newHead;
}
}
方法二:简化空间复杂度,第一步复制各节点然后重组,第二步更新random指针,第三步拆分两个链表。时间O(n),空间O(1)。
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Node cur = head;
Node temp;
// 1.create new Linked List
while (cur!=null) {
temp = cur.next;
cur.next = new Node(cur.val);
cur.next.next = temp;
cur = temp;
}
// 2.change the random point of new list
// random point :cur -> cur.random & cur.next -> cur.random.next
cur = head;
while(cur!=null){
if(cur.random!=null) {
cur.next.random = cur.random.next;
} else {
cur.next.random = null;
}
cur = cur.next.next;
}
// 3.separate two list
cur = head;
Node ans = cur.next;
while(cur.next!=null){
temp = cur.next; // the next Node
cur.next = temp.next;
cur = temp;
}
return ans;
}
}
第 3 天 字符串(简单)
剑指 Offer 05. 替换空格
剑指 Offer 05. 替换空格
先设置一个三倍的数组,之后遇到空格连续赋值为%20。
剑指 Offer 58 - II. 左旋转字符串
剑指 Offer 58 - II. 左旋转字符串
java中string为不可变对象,但是在c++中可以使用原地替换使空间复杂度降到O(1)。
// abcdefg k=2
reverse_string(s, 0, length-1); // gfedcba
reverse_string(s, 0, length-n-1); // cdefgba
reverse_string(s, length-n, length-1); // cdefgab
第 4 天 查找算法(简单)
剑指 Offer 03. 数组中重复的数字
剑指 Offer 03. 数组中重复的数字
方法一:由于说了数字范围不会超过length,因此可以把对应的数字交换到相同索引上,第二次遇到直接说明是重复的。
class Solution {
public int findRepeatNumber(int[] nums) {
int len = nums.length;
for(int i=0;i<len;i++) {
int here = nums[i];
if(here!=i){
if(nums[here]==here){
//the second time a num occurs
return here;
}
// swap nums[i] and nums[here]
int temp = nums[here];
nums[here] = here;
nums[i] = temp;
i -= 1;
}
}
//impossible
return 0;
}
}
剑指 Offer 53 - I. 在排序数组中查找数字 I
剑指 Offer 53 - I. 在排序数组中查找数字 I
方法一:二分找到后,从中间出发向两边一个一个找,最差情况下又成O(n)。
class Solution {
public int search(int[] nums, int target) {
int len = nums.length;
int left=0, right=len-1, mid=(left+right)/2;
int index=-1;
while(left<=right){
mid=(left+right)/2;
if(nums[mid]==target){
index=mid;
break;
}else if(nums[mid]<target){
left=mid+1;
}else{
right=mid-1;
}
}
if(index==-1){
return 0;
}else{
int count = 1;
//the worst condition is all nums equal target
for(int i=index-1;i>=0&&nums[i]==target;i--){
count += 1;
}
for(int i=index+1;i<len&&nums[i]==target;i++){
count += 1;
}
return count;
}
}
}
方法二:利用二分找左右边界,然后相减得到答案。要注意左边界是right-1。
class Solution {
public int search(int[] nums, int target) {
int left = binarySearch(nums,target,true);
int right = binarySearch(nums,target,false);
return right-left-1;
}
// special binary search
// lower is true if want to find left bound
private int binarySearch(int[] nums, int target, boolean lower){
int len = nums.length;
int left=0, right=len-1;
while(left<=right){
int mid = (left+right)/2;
//nums[mid]<=target if right bound
//有点像电子导论里的逻辑电路A!B+!AB二选一,正常二分如果等于就直接返回
if((nums[mid]<target && lower) || (nums[mid]<=target && !lower)){
left = mid+1;
}else{
right = mid-1;
}
}
//左边界一直会找到right=left-1
if(lower) return right;
return left;
}
}
剑指 Offer 53 - II. 0~n-1中缺失的数字
剑指 Offer 53 - II. 0~n-1中缺失的数字
简单二分。
class Solution {
public int missingNumber(int[] nums) {
int len = nums.length;
int left=0, right=len-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]>mid){
right=mid-1;
}else{
left=mid+1;
}
}
return left;
}
}
第 5 天 查找算法(中等)
[M] 剑指 Offer 04. 二维数组中的查找
剑指 Offer 04. 二维数组中的查找
通过20那个例子来看就找到规律了,两次二分即可。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
//先横着找第一个比他小的,再竖着找第一个比他大的,再横着找第一个比他小的,一直找到最后一行,一直锁定左下角
if(matrix.length==0) return false;
int width=matrix[0].length, height=matrix.length;
int left=0,up=0,right=width-1,down=height-1; // right一直减小和up一直增大,因此结束条件是最后一行还找不到,此时up会加1大于down,这是列出界,还有第一个数就大于target,此时right就会为-1,这是行出界,就返回false
while(up<=down && left<=right){
while(left<=right){
int mid=(left+right)/2;
if(matrix[up][mid]==target) return true;
else if(matrix[up][mid]<target) left = mid+1;
else right = mid-1;
}
left=0;
while(up<=down && left<=right){ // 对应行出界
int mid=(up+down)/2;
if(matrix[mid][right]==target) return true;
else if(matrix[mid][right]<target) up = mid+1;
else down = mid-1;
}
down=height-1;
}
return false;
}
}
[不会] 剑指 Offer 11. 旋转数组的最小数字
剑指 Offer 11. 旋转数组的最小数字
如果直接和left相比,left不确定在左还是右,而right确定是在右排序数组的最右。
因此一值和right相比,如果小于right说明mid在右边,如果大于right说明mid在左边,如果等于right则不确定在哪。而且由于重复元素的存在[10,10,10,1,10],[10,1,10,10,10],因此只能right-1来线性更新right。
官方题解有图解释
class Solution {
public int minArray(int[] numbers) {
int len=numbers.length;
int left=0,right=len-1;
// 不加等于最后结束在else if,加了结束在else
while(left<right){
int mid = (left+right)/2;
// 小于的话不能减一,[3,1,2],若mid为1,则mid-1到了左边
if(numbers[mid]<numbers[right]) right=mid;
else if(numbers[mid]>numbers[right]) left=mid+1;
else {
right -= 1;
}
}
return numbers[left];
}
}
剑指 Offer 50. 第一个只出现一次的字符
剑指 Offer 50. 第一个只出现一次的字符
两个数组,一个记录次数,一个记录顺序。用hash有更好的?没看。
class Solution {
public char firstUniqChar(String ss) {
int len = ss.length();
char[] s = ss.toCharArray();
// 记录出现顺序
char[] order = new char[26];
int size = 0;
// 记录出现次数
int[] count = new int[26];
for(int i=0;i<len;i++){
int index = c2i(s[i]);
if(count[index]==0){
order[size] = s[i];
size += 1;
}
count[index] += 1;
}
for(int i=0;i<size;i++){
char ans = order[i];
if(count[c2i(ans)]==1) return ans;
}
return ' ';
}
private int c2i(char c){
return c-'a';
}
}
第 6 天 搜索与回溯算法(简单)(广搜)
剑指 Offer 32 - I. 从上到下打印二叉树
剑指 Offer 32 - I. 从上到下打印二叉树
层次遍历bfs简单用queue。
/**
* 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) {
Queue<TreeNode> q = new LinkedList<TreeNode>();
ArrayList<Integer> ans = new ArrayList<Integer>();
if(root!=null) q.offer(root);
while(!q.isEmpty()){
TreeNode node = q.poll();
ans.add(node.val);
if(node.left!=null) q.offer(node.left);
if(node.right!=null) q.offer(node.right);
}
int[] res = new int[ans.size()];
for(int i = 0; i < ans.size(); i++)
res[i] = ans.get(i);
return res;
}
}
[注意泛型使用] 剑指 Offer 32 - II. 从上到下打印二叉树 II
剑指 Offer 32 - II. 从上到下打印二叉树 II
incompatible types: ArrayList<ArrayList> cannot be converted to List<List>
这个错误出现在我试图用一个 ArrayList<ArrayList>() new 一个 List<List> 对象的时候,把第二个 ArrayList 改成 List ,错误就没有了,那么原理是什么呢,是因为他俩是并列的,类似double和int相对与number是同级的。
来自一个详细解释(泛型)
/**
* 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) {
Queue<TreeNode> q = new LinkedList<TreeNode>();
ArrayList<List<Integer>> ans = new ArrayList<List<Integer>>();
ArrayList<Integer> part = new ArrayList<Integer>();
// 由于最后循环外加了一次part,所以成了二维[[]],所以在这返回
if(root==null) return ans;
TreeNode flag = new TreeNode(0);
flag.left = flag;
q.offer(root);
q.offer(flag);
// 判断应该是大小为1,即只有分隔符
while(q.size()>1){
TreeNode node = q.poll();
if(node.left == node){
q.offer(flag);
ans.add(part);
part = new ArrayList<Integer>();
continue;
}
part.add(node.val);
if(node.left!=null) q.offer(node.left);
if(node.right!=null) q.offer(node.right);
}
// 最后一层在循环里没加进去
ans.add(part);
return ans;
}
}
[M] 剑指 Offer 32 - III. 从上到下打印二叉树 III
剑指 Offer 32 - III. 从上到下打印二叉树 III
简单加了个reverse函数?
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> q = new LinkedList<TreeNode>();
ArrayList<List<Integer>> ans = new ArrayList<List<Integer>>();
ArrayList<Integer> part = new ArrayList<Integer>();
if(root==null) return ans;
TreeNode flag = new TreeNode(0);
flag.left = flag;
q.add(root);
q.add(flag);
int count = 0;
while(q.size()>1){
TreeNode node = q.poll();
if(node.left == node){
q.add(flag);
if(count==1) part = reverse(part);
count = (count+1)%2;
ans.add(part);
part = new ArrayList<Integer>();
continue;
}
part.add(node.val);
if(node.left!=null) q.add(node.left);
if(node.right!=null) q.add(node.right);
}
if(count==1) part = reverse(part);
ans.add(part);
return ans;
}
private ArrayList<Integer> reverse(ArrayList<Integer> list){
int len = list.size();
int left=0,right=len-1;
while(left<right){
Integer temp = list.get(right);
list.set(right, list.get(left));
list.set(left, temp);
left += 1;
right -= 1;
}
return list;
}
}
第 7 天 搜索与回溯算法(简单)(深搜)
[M] 剑指 Offer 26. 树的子结构
剑指 Offer 26. 树的子结构
主体遍历,下部分递归,注意A或B为空时返回true还是false。许多if可以简化到一句。
未简化版
/**
* 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) {
// 直接用前中后遍历的字符串也可以判断? 不行,A的东西比B多,可能穿插
// 首先遍历A这个树
if(A==null || B==null) return false;
if(A!=null && B!=null && A.val==B.val){
boolean a = isSameStructure(A,B);
if(a) return true;
}
boolean b = isSubStructure(A.left,B);
boolean c = isSubStructure(A.right,B);
if(b||c) return true;
else return false;
}
// root is the same
private boolean isSameStructure(TreeNode A, TreeNode B) {
// 跟着B走
if(A==null && B!=null) return false;
if(B==null) return true;
if(A.val!=B.val) return false;
else return isSameStructure(A.left, B.left) && isSameStructure(A.right, B.right);
}
}
简化版:
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A!=null && B!=null) && (isSameStructure(A,B) ||
isSubStructure(A.left,B) || isSubStructure(A.right,B));
}
private boolean isSameStructure(TreeNode A, TreeNode B) {
if(B==null) return true;
if(A==null || A.val!=B.val) return false;
else return isSameStructure(A.left, B.left) && isSameStructure(A.right, B.right);
}
}
剑指 Offer 27. 二叉树的镜像
剑指 Offer 27. 二叉树的镜像
简单swap。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
// 递归swap?
if(root==null) return null;
TreeNode node;
node=root.right;
root.right=root.left;
root.left=node;
mirrorTree(root.left);
mirrorTree(root.right);
return root;
}
}
[没想到] 剑指 Offer 28. 对称的二叉树
剑指 Offer 28. 对称的二叉树
方法一:暴力复制反转再对比,空间复杂度太大。
class Solution {
public boolean isSymmetric(TreeNode root) {
// 先做一个镜像再判断?
TreeNode mirrorTree = mirrorTree(copy(root));
return isSameTree(root,mirrorTree);
}
private TreeNode copy(TreeNode root) {
if(root==null) return null;
TreeNode newRoot = new TreeNode(root.val);
newRoot.left = copy(root.left);
newRoot.right = copy(root.right);
return newRoot;
}
private TreeNode mirrorTree(TreeNode root) {
if(root==null) return null;
TreeNode node;
node=root.right;
root.right=root.left;
root.left=node;
mirrorTree(root.left);
mirrorTree(root.right);
return root;
}
private boolean isSameTree(TreeNode A, TreeNode B) {
if(A==null && B==null) return true;
return A!=null && B!=null && A.val==B.val &&
isSameTree(A.left,B.left) && isSameTree(A.right,B.right);
}
}
方法二:从顶至下一对对看。下面看false和true的情况。
False:
1. only one of them is null
2. the values are not the same
3. only one point cross the leaf node
True:
1. cross the leaf node
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return recur(root.left,root.right);
}
private 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);
}
}
第 8 天 动态规划(简单)
剑指 Offer 10- I. 斐波那契数列
剑指 Offer 10- I. 斐波那契数列
简单dp。
class Solution {
public final int MOD = 1000000007;
public int fib(int n) {
int[] F = new int[101];
F[0] = 0;
F[1] = 1;
for(int i=2;i<=n;i++){
F[i] = (F[i-1] + F[i-2])%MOD;
}
return F[n];
}
}
剑指 Offer 10- II. 青蛙跳台阶问题
剑指 Offer 10- II. 青蛙跳台阶问题
dp[n] = dp[n-1]+dp[n-2];
dp[0] = 1, dp[1] = 1;
class Solution {
public int MOD = 1000000007;
public int numWays(int n) {
int[] dp = new int[101];
dp[0] = 1;
dp[1] = 1;
for(int i=2;i<=n;i++){
dp[i] = (dp[i-1] + dp[i-2])%MOD;
}
return dp[n];
}
}
[M] 剑指 Offer 63. 股票的最大利润
剑指 Offer 63. 股票的最大利润
方法一:左扫最小值,右扫最大值,相减得答案。遍历了三遍。
class Solution {
public int maxProfit(int[] prices) {
int num = prices.length;
if(num==0) return 0;
// start from left
int[] min = new int[num];
min[0] = prices[0];
// start from right
int[] max = new int[num];
max[num-1] = prices[num-1];
for(int i=1;i<num;i++){
min[i] = min[i-1]<prices[i] ? min[i-1] : prices[i];
}
for(int i=num-2;i>=0;i--){
max[i] = max[i+1]>prices[i] ? max[i+1] : prices[i];
}
int ans = 0;
for(int i=0;i<num;i++){
int temp = max[i]-min[i];
ans = temp>ans ? temp : ans;
}
return ans;
}
}
方法二:动态规划。dp[i]代表前i天最高利润。
dp[i] = max( dp[i-1], prices[i]-min(prices[0:i-1]) )
dp[0] = 0
优化:由于dp[i]只和dp[i-1]有关,因此可以用变量profit大题dp[i-1],又min(prices[0:i-1])也可以通过一个数cost来代替。
profit = max( profit, prices[i]-cost)
cost = min(cost, prices[i-1])
这里cost没有和prices[i]比是因为即使prices[i]-cost减下来是负数,也有一开始的profit=0代替掉。
class Solution {
public int maxProfit(int[] prices) {
int num = prices.length;
if(num==0) return 0;
int profit = 0;
int cost = prices[0];
for(int i=1;i<num;i++){
profit = Math.max(profit, prices[i]-cost);
cost = Math.min(cost, prices[i]);
}
return profit;
}
}
[E] 121. 买卖股票的最佳时机(同上)
[121. 买卖股票的最佳时机(同上)]https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
[M] 122. 买卖股票的最佳时机 II
122. 买卖股票的最佳时机 II
多次买卖一支股票问题。
方法一:动态规划。buy代表有股票。
dp[day, buy] = max( dp[day-1,buy], dp[day-1, sell] - prices[day] )
dp[day, sell] = max( dp[day-1, sell], dp[day-1, buy] + prices[day] )
但发现day只与day-1有关,因此可以简化。
buy = max(oldbuy, oldsell - prices[day])
sell = max(oldsell, oldbuy + prices[day])
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int buy = -prices[0], sell = 0;
for(int day=0; day<len; day++){
int oldbuy = buy, oldsell = sell;
buy = Math.max(oldbuy, oldsell - prices[day]);
sell = Math.max(oldsell, oldbuy + prices[day]);
}
return sell;
}
}
方法二:画图就发现和哪天卖没关系,就是把所有上升段加起来,和同一天买卖不冲突。
class Solution {
public int buy=0, sell=1;
public int maxProfit(int[] prices) {
int len = prices.length;
int profit = 0;
for(int i=1;i<len;i++){
int minus = prices[i]-prices[i-1];
if(minus>0) profit += minus;
}
return profit;
}
}
[H] 123. 买卖股票的最佳时机 III
123. 买卖股票的最佳时机 III
方法一:和第二题基本一样,不过要注意设置第一次和第二次同时开始,这样第二次遇到第一次卖完时就会自动换到收益更高的方法。
dp[day][flag]代表前day天最多赚多少,flag可以为0,1,2,3,代表第一次买入、卖出,第二次买入和卖出
dp[day][0] = max(-prices[day], dp[day-1][0]);
dp[day][1] = max(dp[day-1][0] + prices[day], dp[day-1][1]);
dp[day][2] = max(dp[day-1][1] - prices[day], dp[day-1][2]);
dp[day][3] = max(dp[day-1][2] + prices[day], dp[day-1][3]);
dp[0][0] = -prices[0];
dp[2][0] = -prices[0];
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][4];
dp[0][0] = -prices[0];
// 防止第二轮比第一轮先开始,最多也是同时开始
dp[0][2] = -prices[0];
for(int day=1;day<len;day++){
dp[day][0] = Math.max(-prices[day], dp[day-1][0]);
dp[day][1] = Math.max(dp[day-1][0] + prices[day], dp[day-1][1]);
dp[day][2] = Math.max(dp[day-1][1] - prices[day], dp[day-1][2]);
dp[day][3] = Math.max(dp[day-1][2] + prices[day], dp[day-1][3]);
}
return dp[len-1][3];
}
}
同理简化空间复杂度
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int dp0=-prices[0],dp1=0,dp2=-prices[0],dp3=0;
int odp0=dp0,odp1=dp1,odp2=dp2,odp3=dp3;
for(int day=1;day<len;day++){
odp0=dp0;odp1=dp1;odp2=dp2;odp3=dp3;
dp0 = Math.max(-prices[day], odp0);
dp1 = Math.max(odp0 + prices[day], odp1);
dp2 = Math.max(odp1 - prices[day], odp2);
dp3 = Math.max(odp2 + prices[day], odp3);
}
return dp3;
}
}
[H] 188. 买卖股票的最佳时机 IV
188. 买卖股票的最佳时机 IV
一模一样的题
class Solution {
public int maxProfit(int k, int[] prices) {
int len = prices.length;
if(len==0 || k==0) return 0;
int[] dp = new int[2*k];
int[] odp = new int[2*k];
for(int i=0;i<k;i++){
dp[i*2] = -prices[0];
}
for(int day=1;day<len;day++){
for(int i=0;i<2*k;i++){
odp[i] = dp[i];
}
dp[0] = Math.max(-prices[day], odp[0]);
dp[1] = Math.max(odp[0] + prices[day], odp[1]);
for(int i=1;i<k;i++){
dp[2*i] = Math.max(odp[2*i-1] - prices[day], odp[2*i]);
dp[2*i+1] = Math.max(odp[2*i] + prices[day], odp[2*i+1]);
}
}
return dp[2*k-1];
}
}
[M] 309. 最佳买卖股票时机含冷冻期
309. 最佳买卖股票时机含冷冻期
加冷冻期的状态即可。
buy->sell -->> buy->sell->cool
dp[day][0] = Math.max(dp[day-1][2] - prices[day], dp[day-1][0]);
dp[day][1] = Math.max(dp[day-1][0] + prices[day], dp[day-1][1]);
dp[day][2] = Math.max(dp[day-1][1], dp[day-1][2]);
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][3];
dp[0][0] = -prices[0];
for(int day=1;day<len;day++){
dp[day][0] = Math.max(dp[day-1][2] - prices[day], dp[day-1][0]);
dp[day][1] = Math.max(dp[day-1][0] + prices[day], dp[day-1][1]);
dp[day][2] = Math.max(dp[day-1][1], dp[day-1][2]);
}
return dp[len-1][1];
}
}
简化空间。
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int dp0=0,dp1=0,dp2=0;
int odp0=0,odp1=0,odp2=0;
dp0 = -prices[0];
for(int day=1;day<len;day++){
odp0=dp0;odp1=dp1;odp2=dp2;
dp0 = Math.max(odp2 - prices[day], odp0);
dp1 = Math.max(odp0 + prices[day], odp1);
dp2 = Math.max(odp1, odp2);
}
return dp1;
}
}
[M] 714. 买卖股票的最佳时机含手续费
714. 买卖股票的最佳时机含手续费
和第二题一样,只是买入的时候price还要加上fee。
dp[day][0] = max( dp[day-1][1] - prices[i] - fee, dp[day-1][0] )
dp[day][1] = max( dp[day-1][0] + prices[0], dp[day-1][1] )
直接简化空间复杂度
class Solution {
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
int dp0=-prices[0]-fee, dp1=0, odp0=dp0, odp1=dp1;
for(int day=1;day<len;day++){
odp0=dp0;
odp1=dp1;
dp0 = Math.max(odp0, odp1-prices[day]-fee);
dp1 = Math.max(odp1, odp0+prices[day]);
}
return dp1;
}
}
第 9 天 动态规划(中等)
剑指 Offer 42. 连续子数组的最大和
剑指 Offer 42. 连续子数组的最大和
方法一:二维dp,O(n),0代表这个不选
dp[i][1] = max (dp[i-1][1] + nums[i], nums[i])
dp[i][0] = max (dp[i-1][0], dp[i-1][1])
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int[][] dp = new int[len][2];
dp[0][0] = nums[0];
dp[0][1] = nums[0];
for(int i=1;i<len;i++){
dp[i][1] = Math.max(dp[i-1][1]+nums[i], nums[i]);
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);
}
return Math.max(dp[len-1][0], dp[len-1][1]);
}
}
简化一下,时间就减少很多,应该是二维存取太慢。
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int dp0 = nums[0], dp1 = nums[0];
for(int i=1;i<len;i++){
int odp0 = dp0, odp1 = dp1;
dp1 = Math.max(odp1+nums[i], nums[i]);
dp0 = Math.max(odp0, odp1);
}
return Math.max(dp0,dp1);
}
}
方法二:定义可以改为dp[i]代表包含i的最大连续和,用一个变量来保存i之前的最大连续和,就不用dp[i][0]来保存了。
dp = max( dp+nums[i], nums[i)]
res = max(dp, res)
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int dp = nums[0], res = nums[0];
for(int i=1;i<len;i++){
dp = Math.max(dp+nums[i], nums[i]);
res = Math.max(dp, res);
}
return res;
}
}
剑指 Offer 47. 礼物的最大价值
剑指 Offer 47. 礼物的最大价值
简单二维dp。
class Solution {
public int maxValue(int[][] grid) {
// dp[i][j] = max(dp[i-1][j], dp[i][j-1])+grid[i][j]
int height = grid.length, width = grid[0].length;
if(height+width==0) return 0;
int[][] dp = new int[height][width];
dp[0][0] = grid[0][0];
for(int i=1;i<height;i++){
dp[i][0] = dp[i-1][0]+grid[i][0];
}
for(int i=1;i<width;i++){
dp[0][i] = dp[0][i-1]+grid[0][i];
}
for(int i=1;i<height;i++){
for(int j=1;j<width;j++){
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])+grid[i][j];
}
}
return dp[height-1][width-1];
}
}
第 10 天 动态规划(中等)
[M] 剑指 Offer 46. 把数字翻译成字符串
剑指 Offer 46. 把数字翻译成字符串
简单的dp,注意是种类,9和99和999都只能转为1种,06不等于6,写个示例找规律也能找到转移方程。最后i只和i-1和i-2有关,可以滚动数组减空间复杂度。
dp[i] = dp[i-1] 直接加一个数字而已,不会有改变
dp[i] = dp[i-1] + dp[i-2]新加的数字能和上一个数字结合,则可以多一种数字组合,和dp[i-2]结合,06这种不能算
1 1
11 2
111 3
1111 5
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int len = s.length();
if(len==2&&num<26) return 2;
if(len<=2) return 1;
int[] dp = new int[len];
dp[0] = 1;
Integer temp = Integer.valueOf(s.substring(0,2));
if(temp<26 && temp>9) dp[1] = 2;
else dp[1] = 1;
for(int i=2;i<len;i++){
dp[i] = dp[i-1];
temp = Integer.valueOf(s.substring(i-1,i+1));
if(temp<26 && temp>9) dp[i] += dp[i-2];
}
return dp[len-1];
}
}
[M] 剑指 Offer 48. 最长不含重复字符的子字符串
剑指 Offer 48. 最长不含重复字符的子字符串
方法一:自己想的dp。
dp[i]为包含第i个的最长,设置res进行比较以保留之前的最大值
if s[i] is not in s[i-dp[i-1]:i+1] -> O(n^2) in time
store in an array to check, using a flag to avoid to clear the array -> O(n)
如果未重复 dp[i] = dp[i-1] + 1;
awpqw -> pqw
如果重复了及上次出现的index大于等于此次字符串开始的index
if(last index of s[i] > i - dp[i-1])
dp[i] = i - last index of s[i];
(last index of s[i]) stores in check array
如果这样判断check[s[i]-‘a’]!=-1,则在每次更新扫的时候要遍历这个节点以前的字符更新为-1
注意,不仅包括小写字母,所以要用hashmap
这时候dp[i]只和dp[i-1]有关,可以简化,时间复杂度成O(n),空间复杂度为O(1)。
class Solution {
public int lengthOfLongestSubstring(String string) {
int len = string.length();
if(len<1) return 0;
HashMap<Character, Integer> check = new HashMap<Character, Integer>();
int[] dp = new int[len];
char[] s = string.toCharArray();
dp[0] = 1;
check.put(s[0],0);
int res = 1;
for(int i=1;i<len;i++){
if(check.containsKey(s[i]) && check.get(s[i])>=i-dp[i-1]){
//顺序不能反
res = Math.max(res, dp[i-1]);
dp[i] = i - check.get(s[i]);
} else {
dp[i] = dp[i-1] + 1;
}
check.put(s[i],i);
}
return Math.max(res, dp[len-1]);
}
}
方法二:双指针,check.get(s[i])>=i-dp可以用一个指针代替左边界。直接用max(left, check.get(s[i]))来更新左指针。比如[pwwkew]中,左指针分别为-1,-1,1,1,1,2。注意左指针应该在字符串左边,这样ww就不会造成相减等于0。
class Solution {
public int lengthOfLongestSubstring(String string) {
int len = string.length();
HashMap<Character, Integer> check = new HashMap<Character, Integer>();
int left = -1, res = 0;
char[] s = string.toCharArray();
for(int i=0;i<len;i++){
if(check.containsKey(s[i])){
left = Math.max(check.get(s[i]),left);
}
res = Math.max(i-left, res);
check.put(s[i],i);
}
return res;
}
}
第 11 天 双指针(简单)
剑指 Offer 18. 删除链表的节点
剑指 Offer 18. 删除链表的节点
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode node = head;
if(node.val == val) return head.next;
ListNode pre = node;
while(node.next != null){
node = node.next;
if(node.val==val){
pre.next = node.next;
return head;
}
pre = node;
}
return head;
}
}
剑指 Offer 22. 链表中倒数第k个节点
剑指 Offer 22. 链表中倒数第k个节点
双指针保存移动左边的点。
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode res = head, node = head;
int count = 1;
while(node.next != null){
node = node.next;
count += 1;
if(count>k){
res = res.next;
}
}
return res;
}
}
第 12 天 双指针(简单)
[不会] 剑指 Offer 25. 合并两个排序的链表
剑指 Offer 25. 合并两个排序的链表
加一个头结点,一个一个加进去,当时没想到头结点,想复杂了。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0), cur = head;
while(l1!=null && l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
cur = cur.next;
}else{
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
}
if(l1!=null){
cur.next = l1;
}else{
cur.next = l2;
}
return head.next;
}
}
剑指 Offer 52. 两个链表的第一个公共节点
剑指 Offer 52. 两个链表的第一个公共节点
方法一:要求时间是n,空间是1,因此一开始看下两者长度,只有长度相同的后半部分才可能出现相等。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int l1=getLen(headA), l2=getLen(headB);
int minus = l1-l2;
ListNode n1=headA, n2=headB;
while(minus<0){
n2 = n2.next;
minus += 1;
}
while(minus>0){
n1 = n1.next;
minus -= 1;
}
while(n1!=null){
if(n1==n2) return n1;
n1 = n1.next;
n2 = n2.next;
}
return null;
}
private int getLen(ListNode l){
int count = 0;
while(l!=null){
count += 1;
l = l.next;
}
return count;
}
}
方法二:一个指针先走完A再走B,另一个先走B再走A,设A为a+c,c为公共长度,则B为b+c。
若c不是0,这两个指针相遇前走的长度分别为a+c+b,a+b+c。若c等于0,则最后都指向null。
复杂度差不了多少,方法一getlen走的步数为a+c+a+b,第二次找走了a+b次。
第 13 天 双指针(简单)
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
class Solution {
public int[] exchange(int[] nums) {
int len = nums.length;
int left=0, right=len-1;
while(left<right){
while(nums[left]%2==1 && left<right) left+=1;
while(nums[right]%2==0 && left<right) right-=1;
int temp=nums[left];
nums[left] = nums[right];
nums[right] = temp;
left += 1;
right -= 1;
}
return nums;
}
}
剑指 Offer 57. 和为s的两个数字
剑指 Offer 57. 和为s的两个数字
要求了递增,右指针先确定边界(nums[len]+nums[0]<=target)。
之后两指针相加,若大于,则right–,小于则left++。
class Solution {
public int[] twoSum(int[] nums, int target) {
int len=nums.length, right=len-1, left=0;
while(nums[right]>target || nums[right]+nums[0]>target){
right -= 1;
}
while(left<right){
int l=nums[left],r=nums[right],s=l+r;
if(s<target) left+=1;
else if(s>target) right-=1;
else return new int[]{l,r};
}
return new int[0];
}
}
剑指 Offer 58 - I. 翻转单词顺序
剑指 Offer 58 - I. 翻转单词顺序
方法一:存都list再反向输出。
class Solution {
public String reverseWords(String s) {
ArrayList<String> list = new ArrayList<String>();
int len = s.length(), count=0, flag=2, left=0;
int right=len;//防止全是空格
for(int i=0;i<len;i++){
if(s.charAt(i)!=' ' && flag==2){
left = i;
flag -= 1;
}else if(s.charAt(i)==' ' && flag==1){
right = i;
list.add(s.substring(left,right));
count += 1;
flag = 2;
}
}
if(flag==1){
list.add(s.substring(left,len));
count += 1;
}
// 否则说明最后全是空格
StringBuilder sb = new StringBuilder(0);
for(int i=count-1;i>0;i--){
sb.append(list.get(i)+' ');
}
if(list.size()>0) sb.append(list.get(0));
return sb.toString();
}
}
方法二:从后往前,只走一轮。
class Solution {
public String reverseWords(String s) {
s = s.trim();
ArrayList<String> list = new ArrayList<String>();
StringBuilder sb = new StringBuilder(0);
int len = s.length(), right=len-1, left=right;
while(right>=0){
while(left>=0 && s.charAt(left)!=' ') left-=1;
sb.append(s.substring(left+1,right+1) + " ");
while(left>=0 && s.charAt(left)==' ') left-=1;
right = left;
}
return sb.toString().trim();
}
}
第 14 天 搜索与回溯算法(中等)
[M] 剑指 Offer 12. 矩阵中的路径
剑指 Offer 12. 矩阵中的路径
方法一:正常dfs,复杂度高。
class Solution {
int[][] flag = new int[200][200];
char[][] board = new char[200][200];
int height, width;
int[] dx = new int[]{-1,0,1,0};
int[] dy = new int[]{0,1,0,-1};
String word = null;
int length = 0;
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
this.length = word.length();
this.height = board.length;
this.width = board[0].length;
if(length==0) return false;
for(int i=0;i<height;i++){
for(int j=0;j<width;j++){
if(word.charAt(0)==board[i][j] && dfs(i,j,0)){
return true;
}
}
}
return false;
}
private boolean dfs(int x, int y, int index){
//不能是index==length
if(index==length-1) return true;
flag[x][y] = 1;
for(int i=0;i<4;i++){
int nx = x+dx[i];
int ny = y+dy[i];
// word.charAt(0)==board[i][j]放到这统一判断,保证到最后一个字符时,index==length-1成立且字符相等
if(nx>=0&&nx<height&&ny>=0&&ny<width
&& flag[nx][ny]==0
&& word.charAt(index+1)==board[nx][ny]
&& dfs(nx,ny,index+1)){
return true;
}
}
flag[x][y] = 0;
return false;
}
}
方法二WRONG:通过保存此点出发为false来剪枝。比如
AAA
ABA
AAA
若目标为ABC,则可提前记住B为index==1时,从B出发无法返回true。
!这样是不对的,因为第二次到同样的地方时,可能方向是反的。第一次是1->2->3不对设置2位置index为2时不能达到,但4->2->1是对的,第一次知识因为1已经走过了,导致2到不了。
但时间用时较长,不知为什么,可能是因为初始化变量,或是if中有太多and而不是or。
[M]剑指 Offer 13. 机器人的运动范围
剑指 Offer 13. 机器人的运动范围
方法一:简单bfs,时间和空间都很差。
class Solution {
int[] dx = new int[]{-1,0,1,0};
int[] dy = new int[]{0,1,0,-1};
public int movingCount(int m, int n, int k) {
Queue<Integer> q = new LinkedList<Integer>();
int x=0,y=0;
int count=0;
int[][] flag = new int[m][n];
flag[0][0] = 1;
q.add(x);
q.add(y);
while(!q.isEmpty()){
x=q.poll();
y=q.poll();
if(judge(x,y,k)){
count += 1;
for(int i=0;i<4;i++){
if(bound(x+dx[i],y+dy[i],m,n)&&flag[x+dx[i]][y+dy[i]]==0){
q.add(x+dx[i]);
q.add(y+dy[i]);
flag[x+dx[i]][y+dy[i]] = 1;
}
}
}
}
return count;
}
private boolean judge(int x, int y, int k){
int count = 0;
while(x>0){
count += x%10;
x = x/10;
}
while(y>0){
count += y%10;
y = y/10;
}
return count<=k;
}
private boolean bound(int x, int y, int m, int n){
return x>=0 && x<m && y>=0 && y<n;
}
}
第 15 天 搜索与回溯算法(中等)
[M] 剑指 Offer 34. 二叉树中和为某一值的路径
剑指 Offer 34. 二叉树中和为某一值的路径
回溯,注意判断root为叶节点,而非root是null,否则会加两次。
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int target) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
dfs(root,target,ans,temp,0);
return ans;
}
private void dfs(TreeNode root, int target, List<List<Integer>> ans, List<Integer> temp, int index){
if(root==null) return;
temp.add(root.val);
if(root.left==root.right && root.left==null && target==root.val){
List<Integer> ntemp = new ArrayList<Integer>();
Collections.addAll(ntemp,new Integer[temp.size()]);
Collections.copy(ntemp,temp);
ans.add(ntemp);
}
dfs(root.left, target-root.val, ans, temp, index+1);
dfs(root.right, target-root.val, ans, temp, index+1);
temp.remove(index);
}
}
[不会][M] 剑指 Offer 36. 二叉搜索树与双向链表
剑指 Offer 36. 二叉搜索树与双向链表
二叉搜索数BST,左小右大。中序遍历就是从小到大。
左子树应该返回最右节点,右子树应该返回最左节点。若对应的为空,则返回root。
这样不对,遇到[4,1,null,null,2,null,3]这种123在4左边时,应该返回的是右节点,但123这种由于是右子树,返回的是左节点。
应该把右节点放到根节点,然后根节点拉d
最终用中序遍历加几行代码来解决。中序遍历结果中,pre.right=cur, cur.left=pre。
class Solution {
Node head, pre;
public Node treeToDoublyList(Node root) {
if(root==null) return null;
midTrip(root);
// pre==last
head.left = pre;
pre.right = head;
return head;
}
public void midTrip(Node cur) {
if(cur==null) return;
midTrip(cur.left);
if(head==null) head = cur;
cur.left = pre;
if(pre!=null) pre.right = cur;
pre = cur;
midTrip(cur.right);
}
}
剑指 Offer 54. 二叉搜索树的第k大节点
剑指 Offer 54. 二叉搜索树的第k大节点
递归中序反着来。
class Solution {
int count = 0, ans = 0, k;
public int kthLargest(TreeNode root, int k) {
this.k = k;
midTrip(root);
return ans;
}
private void midTrip(TreeNode root) {
if(root==null || count==k) return;
midTrip(root.right);
count += 1;
if(count==k){
ans = root.val;
return;
}
midTrip(root.left);
}
}
最后
以上就是冷静鸵鸟为你收集整理的剑指offer简单(java)上第1天 栈与队列(简单)第 2 天 链表(简单)第 3 天 字符串(简单)第 4 天 查找算法(简单)第 5 天 查找算法(中等)第 6 天 搜索与回溯算法(简单)(广搜)第 7 天 搜索与回溯算法(简单)(深搜)第 8 天 动态规划(简单)第 9 天 动态规划(中等)第 10 天 动态规划(中等)第 11 天 双指针(简单)第 12 天 双指针(简单)第 13 天 双指针(简单)第 14 天 搜索与回溯算法(中等)第 15 天 搜索与回溯算法(中等)的全部内容,希望文章能够帮你解决剑指offer简单(java)上第1天 栈与队列(简单)第 2 天 链表(简单)第 3 天 字符串(简单)第 4 天 查找算法(简单)第 5 天 查找算法(中等)第 6 天 搜索与回溯算法(简单)(广搜)第 7 天 搜索与回溯算法(简单)(深搜)第 8 天 动态规划(简单)第 9 天 动态规划(中等)第 10 天 动态规划(中等)第 11 天 双指针(简单)第 12 天 双指针(简单)第 13 天 双指针(简单)第 14 天 搜索与回溯算法(中等)第 15 天 搜索与回溯算法(中等)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复