该链接的学习计划如下:
剑指 Offer学习计划
题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
思路:
class CQueue {
LinkedList<Integer> stack1;
LinkedList<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<Integer>();
stack2 = new LinkedList<Integer>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
//因为源源不断的数据,如果2还是为空,没有添加进去,则返回为-1
if(stack2.isEmpty()){
return -1;
}else return stack2.pop();
}
}
题目:
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:
各函数的调用总次数不超过 20000 次
注意:本题与主站 155 题相同:https://leetcode-cn.com/problems/min-stack/
思路:
class MinStack {
LinkedList<Integer>inStack;
LinkedList<Integer>minStack;
/** initialize your data structure here. */
//此处的LinkedList不能用addLast和removeLast
public MinStack() {
inStack=new LinkedList<>();
minStack=new LinkedList<>();
//这个栈先压入一个最大的栈帧,主要为了和peek进行比较最小
minStack.push(Integer.MAX_VALUE);
}
public void push(int x) {
inStack.push(x);
//最小栈帧,主要为了时刻比较最小的值,通过和栈顶进行比较
minStack.push(Math.min(minStack.peek(),x));
}
public void pop() {
//出栈的时候,两者都要同时出栈
inStack.pop();
minStack.pop();
}
public int top() {
return inStack.peek();
}
public int min() {
return minStack.peek();
}
}
题目:
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
思路:
class Solution {
public int[] reversePrint(ListNode head) {
//通过list的列表形式
List <Integer> list =new ArrayList <>();
ListNode node=head;
while(node!=null){
list.add(node.val);
node=node.next;
}
int n=list.size();
int[] ss=new int[n];
for(int i=0,j=n-1;i<n&&j>=0;i++,j--){
ss[i]=list.get(j);
}
return ss;
}
}
或者通过栈的形式:
class Solution {
public int[] reversePrint(ListNode head) {
Deque <Integer> stack =new LinkedList <>();
ListNode node=head;
while(node!=null){
stack.push(node.val);
node=node.next;
}
int n=stack.size();
int[] ss=new int[n];
for(int i=0;i<n;i++){
ss[i]=stack.pop();
}
return ss;
}
}
题目:略
思路
class Solution {
public ListNode reverseList(ListNode head) {
//这两个条件缺一不可,因为递归返回值会有越界的那一刻
if(head==null||head.next==null)return head;
//主要是为了给递归做一个返回值
ListNode newhead=reverseList(head.next);
head.next.next=head;
head.next=null;
return newhead;
}
}
或者直接使用迭代
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode ans=cur.next;
cur.next=pre;
//这里是pre放入cur,cur指针放入ans位置中
pre=cur;
cur=ans;
}
return pre;
}
}
题目:
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”
限制:
0 <= s 的长度 <= 10000
思路:
第一种:
class Solution {
public String replaceSpace(String s) {
//通过StringBuilder进行添加
StringBuilder res=new StringBuilder();
for(Character c:s.toCharArray()){
if(c==' ') res.append("%20");
else res.append(c);
}
return res.toString();
}
}
第二种: s.replace(" ", "%20") ;
第三种:
class Solution {
public String replaceSpace(String s) {
int length = s.length();
char[] array = new char[length * 3];
int size = 0;
for (int i = 0; i < length; i++) {
//不用定义成数组,直接定义成字符即可
char c = s.charAt(i);
if (c == ' ') {
array[size++] = '%';
array[size++] = '2';
array[size++] = '0';
} else {
array[size++] = c;
}
}
//之所以要拷贝数组,是因为newstr的数组后面都是0,所以拷贝array 的0-size距离到新的数组
String newStr = new String(array, 0, size);
return newStr;
}
}
题目:
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
示例 2:
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”
限制:
1 <= k < s.length <= 10000
思路:
class Solution {
public String reverseLeftWords(String s, int n) {
//通过StringBuilder的追加形式
StringBuilder ss=new StringBuilder();
for(int i=n;i<s.length();i++){
ss.append(s.charAt(i));
}
for(int i=0;i<n;i++){
ss.append(s.charAt(i));
}
return ss.toString();
}
}
或者通过字符串的切片
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}
以及字符串的拼接:
class Solution {
public String reverseLeftWords(String s, int n) {
String res = "";
for(int i = n; i < s.length(); i++)
res += s.charAt(i);
for(int i = 0; i < n; i++)
res += s.charAt(i);
return res;
}
}
题目:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
思路:
如果使用hashmap
(耗时14ms)
class Solution {
public int findRepeatNumber(int[] nums) {
Map<Integer,Integer>map=new HashMap<>();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
for(Map.Entry<Integer,Integer>entry:map.entrySet()){
if(entry.getValue()==2)return entry.getKey();
}
return nums[0];
}
}
但本身hashset就有去重
耗时(7ms)
class Solution {
public int findRepeatNumber(int[] nums) {
Set <Integer> xx=new HashSet<>();
for(int num:nums){
if(xx.contains(num))return num;
xx.add(num);
}
return 0;
}
}
原地置换,耗时0ms
class Solution {
public int findRepeatNumber(int[] nums) {
int i=0;
//不能使用for,是因为只有满足i等于nums【i】的时候才是排序好了
while(i<nums.length){
if(i==nums[i]){
i++;
continue;
}
//如果已经置换过了,说明原位置已经有重复了,直接输出其值即可
if(nums[nums[i]]==nums[i])return nums[i];
//此处为置换排序,根本的思想其实就是nums[nums[i]]==nums[i]
int temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
return -1;
}
}
题目:
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
注意:本题与主站 34 题相同(仅返回值不同):https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
思路:
该题解主要参考了面试题53 - I. 在排序数组中查找数字 I(二分法,清晰图解)
class Solution {
public int search(int[] nums, int target) {
int l=0,r=nums.length-1;
//搜索右边界相等的值
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<=target)l=mid+1;
else if(nums[mid]>target)r=mid-1;
}
//l与r越界 相反时候,while结束循环,将其右边界的值赋值给right
int right=r;
// 若数组中无 target ,则提前返回
//条件重要
if(r >= 0 && nums[r] != target) return 0;
//搜索左边界相等的值
l=0;
r=nums.length-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<target)l=mid+1;
else if(nums[mid]>=target)r=mid-1;
}
//通过层层逼近,求得左边界的值
int left=l;
return right-left+1;
}
}
class Solution {
public int search(int[] nums, int target) {
return helper(nums, target) - helper(nums, target - 1);
}
int helper(int[] nums, int tar) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= tar) i = m + 1;
else j = m - 1;
}
return i;
}
}
拓展题,求左右边界的范围输出
题目链接如下:
34. 在排序数组中查找元素的第一个和最后一个位置
思路差不多,代码如下:
class Solution {
public int[] searchRange(int[] nums, int target) {
//最开始的初值条件 要先判断好
if(nums.length == 0) return new int[]{-1,-1};
//int []a=new int[2];
int l=0;
int r=nums.length-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<=target)l=mid+1;
else if(nums[mid]>target)r=mid-1;
}
//如果右边界不符合,则直接返回-1,-1
if(r>=0&&nums[r]!=target)return new int[]{-1,-1};
int R=r;
l=0;
r=nums.length-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<target)l=mid+1;
else if(nums[mid]>=target)r=mid-1;
}
//如果左边界不符合,则直接返回-1,-1
//之所以要多判断一次,可能左边界也不符合,会返回越界值
if(l>=0&&nums[l]!=target)return new int[]{-1,-1};
int L=l;
return new int[]{L,R};
}
}
题目:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
限制:
1 <= 数组长度 <= 10000
思路:
下面的时间复杂度比较高,因为顺序遍历的查找
class Solution {
public int missingNumber(int[] nums) {
for(int i=0;i<nums.length;i++){
if(nums[i]!=i){
return i;
}
}
return nums.length;
}
}
之所以是有序的,可以通过二分查找进行遍历
思路链接如下:
面试题53 - II. 0~n-1 中缺失的数字(二分法,清晰图解)
class Solution {
public int missingNumber(int[] nums) {
int l=0,r=nums.length-1;
//通过越界的时候跳出循环 判定r小于l
while(l<=r){
int mid=l+(r-l)/2;
//说明前面的元素都是匹配的,就看后面的元素是否匹配
//直接配对右边的元素,缩小复杂度
//而且即使都符合,通过判定下一个元素的值,也就是left为输出值
if(nums[mid]==mid)l=mid+1;
else r=mid-1;
}
return l;
}
}
题目:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
限制:
0 <= n <= 1000
0 <= m <= 1000
注意:本题与主站 240 题相同:https://leetcode-cn.com/problems/search-a-2d-matrix-ii/
思路:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
//同时遍历,从左下角或者右上角开始遍历(不能选择左上或者右下,因为不好判断)
//matrix.length-1是行长,matrix【0】.length-1是列长
int i=matrix.length-1,j=0;
//判断的初值条件都要满足这个,否则跳出while循环
while(i>=0&&j<matrix[0].length){
//行长度减1
if(matrix[i][j]>target)i--;
//列长度加1
else if(matrix[i][j]<target)j++;
else return true;
}
return false;
}
}
从另外一个角遍历,实现z字形
//区分好 i 与 j的意义,也就是行列别弄混了
int i=0,j=matrix[0].length-1;
while(i<matrix.length&&j>=0){
if(matrix[i][j]>target)j--;
else if(matrix[i][j]<target)i++;
else return true;
}
return false;
题目:
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = “abaccdeff”
输出:‘b’
示例 2:
输入:s = “”
输出:’ ’
限制:
0 <= s 的长度 <= 50000
思路:
使用数组来标记,也可使用哈希进行标记
class Solution {
public char firstUniqChar(String s) {
//先判断初始条件,如果为0,提前返回空
if(s.length()==0)return ' ';
//用数组来标记每个字母的多少个数
int []cc=new int[26];
for(int i=0;i<s.length();i++){
cc[s.charAt(i)-'a']++;
}
//重新遍历一下s这个字符串,而不是遍历cc这个数组,因为返回值是返回s某个字符
//不要遍历错,也不要返回错误的格式
for(int i=0;i<s.length();i++){
if(cc[s.charAt(i)-'a']==1)return s.charAt(i);
}
return ' ';
}
}
一种巧妙的哈希遍历
这部分内容讲解来源于
面试题50. 第一个只出现一次的字符(哈希表 / 有序哈希表,清晰图解)
class Solution {
public char firstUniqChar(String s) {
HashMap<Character, Boolean> dic = new HashMap<>();
char[] sc = s.toCharArray();
for(char c : sc)
//如果有这个值,第一次返回true,第二次返回false
dic.put(c, !dic.containsKey(c));
//第一种方式
//遍历整个字符数组,如果dic这个集合中有这个值的类型为true,则返回
for(char c : sc)
//获取到这个值的时候,如果只有第一次,则返回true,执行该true,返回c这个字符
if(dic.get(c)) return c;
return ' ';
}
}
或者是改变一下Map的结构,使用LinkedHashMap
(在哈希表的基础上,有序哈希表中的键值对是 按照插入顺序排序 的。基于此,可通过遍历有序哈希表,实现搜索首个 “数量为 1的字符”。)
而且遍历map结构的数据,通过转换为set结构
具体部分代码如下Map.Entry
,函数使用getKey以及getValue
class Solution {
public char firstUniqChar(String s) {
HashMap<Character, Boolean> dic = new LinkedHashMap<>();
char[] sc = s.toCharArray();
for(char c : sc)
dic.put(c, !dic.containsKey(c));
for(Map.Entry<Character,Boolean>map:dic.entrySet()){
if(map.getValue())return map.getKey();
}
return ' ';
}
}
题目:
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],
提示:
节点总数 <= 1000
思路:
class Solution {
public int[] levelOrder(TreeNode root) {
//不存在的特殊情况,优先返回,如下情况
if(root == null) return new int[0];
List<Integer>list=new ArrayList<>();
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
TreeNode node =que.poll();
list.add(node.val);
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
int a[]=new int[list.size()];
for(int i=0;i<list.size();i++){
a[i]=list.get(i);
}
return a;
}
}
题目:
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
节点总数 <= 1000
注意:本题与主站 102 题相同:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
思路:
层次遍历的常规代码,加强记忆
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list =new ArrayList<List<Integer>>();
//别忘记最开始的初始条件
if(root==null) return list;
//队列添加node字段
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int n=que.size();
List<Integer> sonlist=new ArrayList<>();
for(int i=0;i<n;i++){
TreeNode node= que.poll();
sonlist.add(node.val);
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
list.add(sonlist);
}
return list;
}
}
题目:
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
返回其层次遍历结果:
提示:
节点总数 <= 1000
思路:
第一种:直接使用Collectons的函数集
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list =new ArrayList<List<Integer>>();
if(root==null)return list;
LinkedList<TreeNode> que=new LinkedList<>();
que.offer(root);
int sum=0;
while(!que.isEmpty()){
int n=que.size();
List<Integer> sonlist=new ArrayList<>();
for(int i=0;i<n;i++){
TreeNode node = que.pop();
sonlist.add(node.val);
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
//判断层次第几层,某个层次直接开始反转
//调用的是Collectons的函数集
if(sum%2==1){
Collections.reverse(sonlist);
}
sum++;
list.add(sonlist);
}
return list;
}
}
题目:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
思路:
知道终止条件以及判断的条件,层层递归即可
关于这个题解,比较全面的解释如下:
具体链接题解如下:
面试题26. 树的子结构(先序遍历 + 包含判断,清晰图解)
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//if(A==null)return false;
//if(B==null)return false;
//事先判断一下root节点,如果两者其中有一个为null则返回false。如果都为null,返回true
if (B == null || A == null) {
return false;
}
//先判断根节点是否为满足以及层层遍历,如果是,则返回true
if(A.val==B.val && dfs(A.left,B.left)&&dfs(A.right,B.right)){
return true;
}
//只需要判断一个方向即可,或并起来
return isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
public boolean dfs(TreeNode A,TreeNode B){
//内部结构,如果B节点先为null则已经判断完了。A为null则返回false
if(B==null)return true;
if(A==null)return false;
if(A.val==B.val){
return dfs(A.left,B.left)&&dfs(A.right,B.right);
}else {
return false;
}
}
}
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//这个写法不像上面,统一一起判断即可。判断值是否相等在递归条件中
//外层调用的函数只要两个都不为null, 而且只满足一个dfs(A,B) 或者递归下一层的结构节点
return (A!=null && B!=null) && (dfs(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B));
}
public boolean dfs(TreeNode A,TreeNode B){
//内部结构,如果B节点先为null则已经判断完了。A为null则返回false
if(B==null)return true;
//如果A为null,则说明还有节点没判断完。或者是两者不相等
if(A==null || A.val!=B.val)return false;
return dfs(A.left,B.left)&&dfs(A.right,B.right);
}
}
思路:
深度优先遍历:
class Solution {
public boolean isSymmetric(TreeNode root) {
return ss(root,root);
}
public boolean ss(TreeNode L1,TreeNode L2){
if(L1==null&&L2==null) return true;
if(L1 ==null ||L2==null) return false;
return L1.val==L2.val && ss(L1.left,L2.right) && ss(L1.right,L2.left);
}
}
广度优先遍历:
class Solution {
public boolean isSymmetric(TreeNode root) {
return bfs(root,root);
}
//返回值类型为boolean
public boolean bfs(TreeNode L1,TreeNode L2){
Queue<TreeNode>que=new LinkedList<>();
que.offer(L1);
que.offer(L2);
//for(int i=0;i
while(!que.isEmpty()){
TreeNode v1=que.poll();
TreeNode v2=que.poll();
//一个个判断过于冗余
//if(v1.left!=null){
// que.offer(v1.left)
//}
//如果两个都为null则继续
if(v1==null&&v2==null)continue;
//这三个条件有哪个条件不满足则返回为false
if(v1==null||v2==null||v1.val!=v2.val)return false;
que.offer(v1.left);
que.offer(v2.right);
que.offer(v1.right);
que.offer(v2.left);
}
return true;
}
}
题目:
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
0 <= 节点个数 <= 1000
注意:本题与主站 226 题相同:https://leetcode-cn.com/problems/invert-binary-tree/
思路:
利用递归的方式,将其root的left指向right节点,但是不能同时将root的right指向left节点,因为节点互换不能这么使用
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
或者用一个标记的点进行标记
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
广度优先遍历:
class Solution {
public TreeNode mirrorTree(TreeNode root) {
//不可以这么使用,返回的是TreeNode类型
//List list=new LinkedList<>();
//if(root==null)return list;
if (root == null) {
return null;
}
Queue <TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int n=que.size();
for(int i=0;i<n;i++){
TreeNode node=que.poll();
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
}
return root;
}
}
题目:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 100
思路:
如果使用下面的方法会出现超时,所以不推荐下面这种动态规划
class Solution {
public int fib(int n) {
if(n==0)return 0;
if(n==1)return 1;
return fib(n-1)+fib(n-2);
}
}
class Solution {
public int fib(int n) {
int a=0,b=1,sum;
for(int i=0;i<n;i++){
//一共10个数
sum=(a+b)%1000000007;
//将b赋值给a,也就是移位
a=b;
b=sum;
}
return a;
}
}
题目:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
注意:本题与主站 121 题相同:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
思路:
class Solution {
public int maxProfit(int[] prices) {
//因为数组长度很大,所以要这样定义
int min=Integer.MAX_VALUE;
int sum=0;
for(int i=0;i<prices.length;i++){
//找到最小的值
if(prices[i]<min){
min=prices[i];
}else {
//如果后面的值都比前面大,则记录开始到现在的最大值,到底有多大
sum=Math.max(prices[i]-min,sum);
}
}
return sum;
}
}
题目:
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
注意:本题与主站 53 题相同:https://leetcode-cn.com/problems/maximum-subarray/
思路:
class Solution {
public int maxSubArray(int[] nums) {
//设置一个刚开始的临时变量值,主要为了前一个值的相加
int pre=0;
//设置一个最大值,主要为了保存其最大值的值
int max=nums[0];
for(int i=0;i<nums.length;i++){
//不可以写成pre=Math.max(pre+nums[i],pre);因为他是连续的,宁愿断开重启一个,也不能断续
pre=Math.max(pre+nums[i],nums[i]);
//每个值都保存其最大的
max=Math.max(pre,max);
}
return max;
}
}
题目:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200
思路:
class Solution {
public int maxValue(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
//不要忽略这一行,不要通过不了
if(i == 0 && j == 0) continue;
if(i==0){
grid[0][j]=grid[0][j-1]+grid[0][j];
}else if(j==0){
grid[i][0]=grid[i-1][0]+grid[i][0];
}else if (i!=0&&j!=0){
grid[i][j]=Math.max(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
}
return grid[m-1][n-1];
}
}
或者利用辅助数组,通过多开一行一列,优化代码
class Solution {
public int maxValue(int[][] grid) {
int row = grid.length;
int column = grid[0].length;
//dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值
int[][] dp = new int[row + 1][column + 1];
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= column; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
return dp[row][column];
}
}
题目:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
s.length <= 40000
注意:本题与主站 3 题相同:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
思路:
class Solution {
public int lengthOfLongestSubstring(String s) {
Set<Character>set=new HashSet<>();
int n=s.length();
int rk=-1,sum=0;
//使用双指针的遍历方式,第一个指针为for循环
for(int i=0;i<n;i++){
if(i!=0){
//每一个集合都是字符串的元素值,而不是下标值,不要疏忽漏掉了
set.remove(s.charAt(i-1));
}
//第二个指针使用while结构,通过rk进行遍历
while(rk+1<n&&!set.contains(s.charAt(rk+1))){
set.add(s.charAt(rk+1));
rk++;
}
//通过第二个指针减去第一个指针既为其长度值
sum=Math.max(sum,rk-i+1);
}
return sum;
}
}
题目:
略
思路:
class Solution {
public ListNode deleteNode(ListNode head, int val) {
//使用头节点主要是因为如果删除的是第一个节点的话
ListNode node=new ListNode(0);
node.next=head;
//使用ans假节点进行遍历
ListNode ans=node;
while(ans.next!=null){
if(ans.next.val==val){
ans.next=ans.next.next;
break;
}else{
ans=ans.next;
}
}
//正确的返回结果应该是头节点,也就是假头节点的下一个节点
return node.next;
}
}
或者使用双指针节点进行遍历
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head.val == val) return head.next;
ListNode pre = head, cur = head.next;
while(cur != null && cur.val != val) {
pre = cur;
cur = cur.next;
}
if(cur != null) pre.next = cur.next;
return head;
}
}
题目:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
思路:
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode a=head;
ListNode b=head;
//a走k个节点
for(int i=0;i<k;i++){
a=a.next;
}
//a不为null的时候,b也一直走,最后输出b即可。追击问题
while(a!=null){
a=a.next;
b=b.next;
}
return b;
}
}
题目:
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
注意:本题与主站 21 题相同:https://leetcode-cn.com/problems/merge-two-sorted-lists/
思路:
创建一个头节点,一个个判断,自个一开始书写的想法:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prev=new ListNode(0);
ListNode prehead=prev;
while(l1!=null||l2!=null){
if(l1==null){
prehead.next=l2;
break;
}else if(l2==null){
prehead.next=l1;
break;
}else if(l1.val>=l2.val){
prehead.next=l2;
l2=l2.next;
}else{
prehead.next=l1;
l1=l1.next;
}
prehead=prehead.next;
}
//头结点的next,记得输出这个格式
return prev.next;
}
}
或者空节点在while循环之后判断:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
也可通过递归遍历的形式输出
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1==null)return list2;
else if (list2 ==null) return list1;
else if (list1.val < list2.val) {
list1.next=mergeTwoLists(list1.next,list2);
return list1;
}
else {
list2.next=mergeTwoLists(list1,list2.next);
return list2;
}
}
}
题目:
省略
思路:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//初始值条件 要一开始就判断
if(headA==null||headB==null){
return null;
}
ListNode a=headA;
ListNode b=headB;
//w'hile 的判断是a!=b,毕竟找的是公共节点
while(a!=b){
if(a!=null){
a=a.next;
}else{
a=headB;
}
if(b!=null){
b=b.next;
}else{
b=headA;
}
}
return a;
}
}
题目:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
0 <= nums.length <= 50000
0 <= nums[i] <= 10000
思路:
类似快排的部分代码
双端开始遍历,前面找偶数,后面找奇数
然后交换即可
class Solution {
public int[] exchange(int[] nums) {
int i=0;
int j=nums.length-1;
while(i<j){
while(i<j && nums[i]%2!=0)i++;
while(i<j && nums[j]%2!=1)j--;
swap(nums,i,j);
}
return nums;
}
public void swap(int[] nums,int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
题目:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
思路:
class Solution {
public int[] twoSum(int[] nums, int target) {
int i=0;
int j=nums.length-1;
//因为本身是递增数组,此处用hashset的复杂度比双指针一次遍历要高
//所以通过 最大的加最小的都比target大,所以最大的数舍弃;最小的加最大的都比target小,所以最小的舍弃
while(i<j){
int sum=nums[i]+nums[j];
if(sum>target)j--;
else if(sum<target)i++;
else return new int[]{nums[i],nums[j]};
}
return new int[0];
}
}
或者使用hashset的结构:(复杂度比上面那个高)
class Solution {
public int[] twoSum(int[] nums, int target) {
Set <Integer> set=new HashSet<>();
for(int i=0;i<nums.length;i++){
if(set.contains(target-nums[i])){
return new int []{nums[i],target-nums[i]};
}
set.add(nums[i]);
}
return new int[0];
}
}
题目:leetcode:剑指 Offer 58 - I. 翻转单词顺序
dazhiyi
class Solutio
public String reverseWords(String s) {
List<String> list=Arrays.asList(s.trim().split("\\s+"));
Collections.reverse(list);
//用join函数将其列表添加空格
return String.join(" ",list);
}
}
或者另外一种做法:
class Solution {
public String reverseWords(String s) {
String[] strs = s.trim().split("\\s+"); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
if(strs[i].equals("")) continue; // 遇到空单词则跳过
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}
}
class Solution{
public String reverseWords(String s) {
StringBuilder sb=new StringBuilder();
int i=s.length()-1,j=i;
//使用双指针进行遍历,主要是从后往前遍历
while(i>=0){
while(i>=0 && s.charAt(i)!=' ')i--;
sb.append(s.substring(i+1,j+1)+' ');
while(i>=0 &&s.charAt(i)==' ')i--;
j=i;
}
return sb.toString().trim();
}
}
题目:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
示例 2:
输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
board 和 word 仅由大小写英文字母组成
注意:本题与主站 79 题相同:https://leetcode-cn.com/problems/word-search/
思路:
class Solution {
public boolean exist(char[][] board, String word) {
int m=board.length;
int n=board[0].length;
//将其字符串 传给字符数组进行遍历
char []c=word.toCharArray();
//通过深度回溯遍历,一个字母一个一个往下遍历,如果最后不满足的时候,再从另外一个节点开始遍历
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
//只有这个条件成立的时候,才会及时返回true,否则一直遍历到条件结束
if(backtrace(board,c,i,j,0))return true;
}
}
return false;
}
public boolean backtrace(char[][]board,char [] word,int i,int j,int k){
//越界条件以及单词不匹配的时候返回false
if(i<0||i>=board.length||j<0||j>=board[0].length||board[i][j]!=word[k])return false;
//数组长度等同于k的数量值的时候才返回为true
if(word.length-1==k)return true;
//在回溯的时候,给予一个状态变量代表已经访问过了
board[i][j]='\0';
boolean falg=backtrace(board,word,i+1,j,k+1)||backtrace(board,word,i-1,j,k+1)||backtrace(board,word,i,j+1,k+1)||backtrace(board,word,i,j-1,k+1);
//回溯结束的时候直接返回为初值值
board[i][j] = word[k];
return falg;
}
}
题目:
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3:
输入:root = [1,2], targetSum = 0
输出:[]
提示:
树中节点总数在范围 [0, 5000] 内
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000
注意:本题与主站 113 题相同:https://leetcode-cn.com/problems/path-sum-ii/
思路:
class Solution {
List<List<Integer>> list=new ArrayList<List<Integer>>();
LinkedList<Integer> sonlist=new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if(root==null)return list;
backtrace(root,target);
return list;
}
public void backtrace(TreeNode root,int target){
//边界值超出的话要返回,通过root为null,返回上一层
if(root==null)return;
//不管有没有,先加入节点,后续在判断
sonlist.addLast(root.val);
//每个节点加入之后要减去
target-=root.val;
//判断条件不只是为0,因为到叶子节点,还需要左右节点都为null,才可添加
if(target==0 && root.left==null && root.right==null){
list.add(new ArrayList<>(sonlist));
//不用带return返回值
}
//标准的回溯,没有到叶子节点,则一直左右节点的跑
backtrace(root.left,target);
backtrace(root.right,target);
//如果执行到这都没有,则把叶子节点删除
sonlist.removeLast();
//并且把刚那个节点加回去
target+=root.val;
}
}
题目:leetcode:剑指 Offer 36. 二叉搜索树与双向链表
大致意思是将其二叉搜索树变为双向链表,并且是排序的
思路:
中序遍历,而且递归 前后节点的链表
class Solution {
Node pre;//上一个节点,递归的cur为当前节点
Node head;//记住头节点的,主要为了双向链表的头尾
public Node treeToDoublyList(Node root) {
if(root==null) return null;
dfs(root);
//双向链表的头尾节点
head.left=pre;
pre.right=head;
return head;
}
public void dfs(Node cur){
if(cur==null)return ;
//左节点遍历
dfs(cur.left);
//根节点遍历,主要为了将其回溯的时候,子节点指向回溯的下一个节点(双向链表的下一个节点)
if(pre!=null)pre.right=cur;
//还可存储根节点
else head=cur;
//当前节点指向回溯上来的上一个节点(双向链表的上个节点)
cur.left=pre;
//pre移位置
pre=cur;
//右节点遍历
dfs(cur.right);
}
}
题目:
给定一棵二叉搜索树,请找出其中第 k 大的节点的值。
限制:
1 ≤ k ≤ 二叉搜索树元素个数
思路:
使用优先队列
class Solution {
//默认是小顶堆,所以输入1423,输出的时候是1234,也就是出栈的时候是1234
//为了修改成大顶堆,需要修改如下
Queue <Integer> que=new PriorityQueue<>((o1,o2)->(o2-o1));
public int kthLargest(TreeNode root, int k) {
dfs(root);
for(int i=1;i<=k;i++){
//在等于k的时候,直接查看peek 可以减少时间内存。
if(i==k)return que.peek();
que.poll();
}
return 0;
}
public void dfs(TreeNode root){
if(root==null)return;
dfs(root.left);
que.offer(root.val);
dfs(root.right);
}
}
或者使用如下
(二叉搜索树的原理)
因为是输出最大的第k个值所以使用先序遍历(从小到大)的反转也就是(右根左)
class Solution {
int res;
int index = 0; //计数器
public int kthLargest(TreeNode root, int k) {
dfs(root,k);
return res;
}
void dfs(TreeNode root ,int k)
{
if(root == null) return;
dfs(root.right,k); //右
index++;
if(k == index) res = root.val; //根
dfs(root.left,k); //左
}
}