目录
前言
该算法链接来源于
冲刺春招-精选笔面试 66 题大通关
以下为我的学习笔记以及汇总,也为了方便其他人更加快速的浏览
第一天
21. 合并两个有序链表(简单)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
示例 2:
示例 3:
提示:
思路
通过递归遍历合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
或者通过创建头节点进行遍历
遍历的同时,头节点 和l1 l2 的下一个位置移动
以及最后谁先结束 prev.next = l1 == null ? l2 : l1;
最后返回的值是头结点的下一个next
通过创建一个头节点,以及头指针遍历
关于怎么创建头节点的知识点可看我之前的文章
java中new ListNode(0)常见用法详细区别(全)
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;
}
}
146. LRU 缓存(中等)
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
输入
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
最多调用 2 * 105 次 get 和 put
思路
主要的思路是 通过哈希表加上双向链表
在java中有一个类也是这样,可以通过继承实现它来写
java之LinkedHashMap源码详细解析
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int key, int value) {this.key = key; this.value = value;}
}
private Map<Integer, DLinkedNode> map = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node= map.get(key);
if(node==null) return -1 ;
// 如果 key 存在,先通过哈希表定位,再移到头部
removeNode(node);
addToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node=map.get(key);
if(node == null){
// 如果 key 不存在,创建一个新的节点
DLinkedNode newnode=new DLinkedNode(key,value);
// 添加进哈希表
map.put(key,newnode);
//添加头部 并且size加1
addToHead(newnode);
size++;
if(size>capacity){
//移除尾部,map删除
DLinkedNode tail=removeTail();
map.remove(tail.key);
size--;
}
}
else{
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;//不可省略
removeNode(node);
addToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
25. K 个一组翻转链表(困难)
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
示例 3:
示例 4:
提示:
列表中节点的数量在范围 sz 内
1 <= sz <= 5000
0 <= Node.val <= 1000
1 <= k <= sz
思路:
关于这篇文章的思路,主要结合反转链表的一部分
具体在于中间的衔接点怎么使用,以及k个,前后顺序的状态
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode pre=dummy;
ListNode end=dummy;
while(end.next!=null){
for(int i=0;i<k&&end!=null;i++){
end=end.next;
}
if(end==null)break;
ListNode next=end.next;
end.next=null;
ListNode start= pre.next;
pre.next=reverse(start);
start.next=next;
pre=start;
end=start;
}
return dummy.next;
}
public ListNode reverse(ListNode rear){
ListNode prev=null;
ListNode rev=rear;
while(rev!=null){
ListNode next=rev.next;
rev.next=prev;
prev=rev;
rev=next;
}
return prev;
}
}
关于以上代码可看如下类似的注解解释:
或者看官方的解释,比较易懂
图解k个一组翻转链表的解释
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null){
return head;
}
//定义一个假的节点。
ListNode dummy=new ListNode(0);
//假节点的next指向head。
// dummy->1->2->3->4->5
dummy.next=head;
//初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
ListNode pre=dummy;
ListNode end=dummy;
while(end.next!=null){
//循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
//dummy->1->2->3->4->5 若k为2,循环2次,end指向2
for(int i=0;i<k&&end != null;i++){
end=end.next;
}
//如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
if(end==null){
break;
}
//先记录下end.next,方便后面链接链表
ListNode next=end.next;
//然后断开链表
end.next=null;
//记录下要翻转链表的头节点
ListNode start=pre.next;
//翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
pre.next=reverse(start);
//翻转后头节点变到最后。通过.next把断开的链表重新链接。
start.next=next;
//将pre换成下次要翻转的链表的头结点的上一个节点。即start
pre=start;
//翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
end=start;
}
return dummy.next;
}
//链表翻转
// 例子: head: 1->2->3->4
public ListNode reverse(ListNode head) {
//单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.next == null){
return head;
}
//前一个节点指针
ListNode preNode = null;
//当前节点指针
ListNode curNode = head;
//下一个节点指针
ListNode nextNode = null;
while (curNode != null){
nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
curNode.next=preNode;//将当前节点next域指向前一个节点 null<-1<-2<-3<-4
preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
}
return preNode;
}
}
第二天
14. 最长公共前缀(简单)
题目:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
示例 2:
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成
思路:
通过对比所有的列,一列一列比较
终止条件有两个(通过两个for进行遍历,而且实时刻刻都是只有一个第一行的每一列进行比较strs[0].charAt(j
)
- 这一列的长度等于其遍历的当前列的长度
strs[i].length()==j
- 不同列的相同位置
strs[i].charAt(j)!=strs[0].charAt(j)
如果执行完都不返回的话,最后就是本身,输出strs【0】即可
字符串数组,有两个区别,取全部的字符串长度,则为strs.length;
,取单个数组内部的字符串则为strs[0].length()
(容易弄混)
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
int m=strs.length;
int n=strs[0].length();
for(int j=0;j<n;j++){//列
for(int i=1;i<m;i++){//行
if(strs[i].length()==j||strs[i].charAt(j)!=strs[0].charAt(j)){
return strs[0].substring(0,j);
}
}
}
return strs[0] ;
}
}
另外一种思路是,对比每一行的,一行一行的比较。最后输出其结果即可
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
int m=strs.length;
String prefix=strs[0];
for(int i=1;i<m;i++){
int length=Math.min(strs[i].length(),prefix.length());
int j;
for(j=0;j<length;j++){
if(strs[i].charAt(j)!=prefix.charAt(j) ){
break;
}
}
prefix=strs[i].substring(0,j);
//int index=0;
//while(index<length&&strs[i].charAt(index)==prefix.charAt(index))index++;
//prefix=strs[i].substring(0,index);
}
return prefix;
}
}
3. 无重复字符的最长子串(中等)
题目:
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
示例 2:
示例 3:
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
思路:
通过遍历数组,为了减少一次遍历,可以通过set集合进行遍历
如果出现重复,则删除第一个节点,继续开始从第二个节点开始遍历(因为从第一个节点到查重重复 是不能算入子串的)
集合已经添加进入了,第二个指针可以不用挪到最开始的地方。
class Solution {
public int lengthOfLongestSubstring(String s) {
Set<Character>set=new HashSet<>();
int n=s.length();
int ans=0,rk=0;
for(int i=0;i<n;i++){
if(i!=0) set.remove(s.charAt(i-1));
while(rk<n&&!set.contains(s.charAt(rk))){
set.add(s.charAt(rk));
rk++;
}
ans=Math.max(ans,rk-i);
}
return ans;
}
}
以上是因为rk的节点,每次都会往右在挪一个节点位置,所以不用减1
如果rk节点要想减1,可以通过下面的代码设置
class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.remove(s.charAt(i - 1));
}
while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
// 不断地移动右指针
occ.add(s.charAt(rk + 1));
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = Math.max(ans, rk - i + 1);
}
return ans;
}
}
第三天
206. 反转链表(简单)
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
示例 2:
示例 3:
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
1.思路一:
ListNode rear可以在任何时刻定义
也可直接使用ListNode rear=cur.next;
返回的是pre指针,而不是cur指针也不是head指针
具体的逻辑思路如下
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur=head,pre=null;
ListNode rear;
while(cur!=null){
rear=cur.next;
cur.next=pre;
pre=cur;
cur=rear;
}
return pre;
}
}
中途的错误做法:
只截取上面片段代码作为讲解:
将rear=cur.next;放在while里面最后定义,rear已经越界了
ListNode cur=head,pre=null;
ListNode rear;
while(cur!=null){
cur.next=pre;
pre=cur;
cur=rear;
rear=cur.next;
}
2.思路二:
使用递归的条件进行反转
递这个用法用在了层层递进
归这个用法用在了每一层的特殊情节,也就是两个链表地址空间的反转
class Solution {
public ListNode reverseList(ListNode head) {
// 1. 递归终止条件
if (head == null || head.next == null) {
return head;
}
ListNode newhead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newhead;
}
199. 二叉树的右视图(中等)
题目:
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
示例 2:
示例 3:
提示:
二叉树的节点个数的范围是 [0,100]
-100 <= Node.val <= 100
思路:
主要的思路是层次遍历的应用,在每一层的最后一个节点输出即可
对层次遍历熟悉就好操作
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer>list=new ArrayList<>();
if(root==null) return list;
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();
if(i==n-1)list.add(node.val);
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
}
return list;
}
}
第四天
1. 两数之和(简单)
题目:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
示例 2:
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?
思路:
常规的思路可以使用暴力遍历,但是复杂度比较高
可以使用哈希表的存储进行存取
通过target-nums【i】进行获取
而且map哈希中是containsKey,返回一个数组类型是new int[]{map.get(target-nums[i]),i};
如果为空值则为return new int[0];
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);
}
return new int[0];
}
}
15. 三数之和(中等)
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
示例 2:
示例 3:
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105
思路:
先排序,步步逼近双指针
最主要的条件是 去重而且一开始大于0都要去掉
通过判定总值 跟0的比较,移动指针的位置,使用while一直移动指针,而且最后还要前进一个,如果是L++ 最后要在进一个L。++L是最合适的。再者使用while的同时 一直不会跳出大循环,所以每个小循环都要L<R
列表中添加数组,主要通过这个函数进行转换:Arrays.asList() 详解
而且需要使用ArrayList而不是List这个函数
list.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[L],nums[R])));
关于这部分的解答 更详细的题解 可看如下链接:
三数之和(排序+双指针,易懂图解)
画解算法:15. 三数之和
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list=new ArrayList<List<Integer>>();
Arrays.sort(nums);
// < nums.length - 2是为了保证后面还能存在两个数字
for(int i=0;i<nums.length-2;i++){
if(nums[i]>0)break;//大于0,则后面的数字也是大于零(排序后是递增的)
if(i>0&&nums[i]==nums[i-1])continue;//值重复了,去重
int L=i+1;
int R=nums.length-1;
while(L<R){
int sum=nums[i]+nums[L]+nums[R];
if(sum==0){
list.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[L],nums[R])));
while(L<R&&nums[L]==nums[++L]);//左指针前进并去重
while(L<R&&nums[R]==nums[--R]);//右指针前进并去重
}else if(sum>0){
while(L<R&&nums[R]==nums[--R]);//右指针前进并去重
}else if(sum<0){
while(L<R&&nums[L]==nums[++L]);//左指针前进并去重
}
}
}
return list;
}
}
第五天
7. 整数反转(中等)
题目
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
示例 2:
示例 3:
示例 4:
提示:
思路一:
这道题的思路很像之前那道算法
【leetcode】数学 - 回文数
但是有个前提条件,而且可以是负数
所以在要加以判断,否则会溢出
class Solution {
public int reverse(int x) {
int res=0;
while(x!=0){
if(res<Integer.MIN_VALUE/10||res>Integer.MAX_VALUE/10){
return 0;
}
int rev=x%10;
x=x/10;
res=res*10+rev;
}
return res;
}
}
215. 数组中的第K个最大元素(中等)
题目:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
示例 2:
提示:
1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104
思路:
使用快排的思路将其遍历出来
具体快排的思路可看我这篇文章
【数据结构】常见排序算法详细分析(内含java与c++代码)
其快排代码思路如下:
class Solution {
public int findKthLargest(int[] nums, int k) {
quicksort(nums,0,nums.length-1);
return nums[nums.length-k];
}
public void quicksort(int [] nums,int left,int right){
if(left>right)return;//越界返回
int l=left;
int r=right;
int temp=nums[l];//基准的数字
while(l<r){
while(l<r&&nums[r]>=temp)r--;//先走右再走左
while(l<r&&nums[l]<=temp)l++;
if(l<r){
int t=nums[l];
nums[l]=nums[r];
nums[r]=t;
}
}
nums[left]=nums[l];//最后的基准 跟(i与j相等)i互换位置
nums[l]=temp;
quicksort(nums,left,l-1); //递归调用
quicksort(nums,l+1,right);
}
}
第六天
33. 搜索旋转排序数组(中等)
题目:
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-104<= nums[i] <= 104
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104
进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?
思路:
采用二分查找的方法
将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。
此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
关于该题解看的是如下提示:
官方题解
class Solution {
public int search(int[] nums, int target) {
int n=nums.length;
int l=0,r=n-1;
while(l<=r){
int mid=(l+r)/2;
if(nums[mid]==target)return mid;
if(nums[0]<=nums[mid]){
if(nums[0]<=target&&target<nums[mid]){
r=mid-1;
}else{
l=mid+1;
}
}else{
if(nums[mid]<target&&target<=nums[n-1]){
l=mid+1;
}else{
r=mid-1;
}
}
}
return -1;
}
}
54. 螺旋矩阵(中等)
题目:
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
示例 2:
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
思路:
一个循环一个循环的输出,同时要在每个循环中加一个判断,随时计数器结束就停止循环,不然会出现下方某个错误
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list=new ArrayList<>();
int m=matrix.length;
int n=matrix[0].length;
int l=0,r=n-1,top=0,bottom=m-1;
int num=1;
int sum=m*n;
while(num<=sum){
for(int i=l;i<=r&&num<=sum;i++){
list.add(matrix[top][i]);
num++;
}
top++;//往下加1
for(int i=top;i<=bottom&&num<=sum;i++){
list.add(matrix[i][r]);
num++;
}
r--;//往左减1
for(int i=r;i>=l&&num<=sum;i--){
list.add(matrix[bottom][i]);
num++;
}
bottom--;//往上加1
for(int i=bottom;i>=top&&num<=sum;i--){
list.add(matrix[i][l]);
num++;
}
l++;//往右加1
}
return list;
}
}
每个条件中要加入&&num<=sum
,否则一个while出不了循环,特别是长方形会有错误
示例拓展:
59. 螺旋矩阵 II
class Solution {
public int[][] generateMatrix(int n) {
int [][] ss=new int[n][n];
int l=0,r=n-1,top=0,bottom=n-1;
int num=1;
int sum=n*n;
while(num<=sum){
for(int i=l;i<=r;i++){
ss[top][i]=num++;
}
top++;//往下走
for(int i=top;i<=bottom;i++){
ss[i][r]=num++;
}
r--;//往左走
for(int i=r;i>=l;i--){
ss[bottom][i]=num++;
}
bottom--;//往上走
for(int i=bottom;i>=top;i--){
ss[i][l]=num++;
}
l++;//往右走
}
return ss;
}
}
第七天
53. 最大子数组和(简单)
题目:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
示例 2:
示例 3:
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
具体思路如下:
通过Math函数,将其一个个加上获取最大值
以及通过Math函数,存储各个区域中的最大子数组之和
通过动态规划的思路进行滚动数组
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],nums[i]);
max=Math.max(pre,max);
}
return max;
}
}
152. 乘积最大子数组(中等)
题目:
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
示例 1:
示例 2:
提示:
1 <= nums.length <= 2 * 104
-10 <= nums[i] <= 10
nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数
因为可能为负数,所以要保存最大值和最小值。再者,定义的总数一开始就不应该为0,而是Integer.MIN_VALUE
如果下一个数为负数,则最大值和最小值互换
具体代码如下:
class Solution {
public int maxProduct(int[] nums) {
int max=1,min=1;
int sum=Integer.MIN_VALUE;
for(int i=0;i<nums.length;i++){
if(nums[i]<0){
int temp=max;
max=min;
min=temp;
}
max=Math.max(nums[i]*max,nums[i]);
min=Math.min(nums[i]*min,nums[i]);
sum=Math.max(sum,max);
}
return sum;
}
}
第八天
20. 有效的括号(简单)
题目:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例 1:
示例 2:
示例 3:
示例 4:
示例 5:
提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
思路:
注意一个代码格式:
Map <Character,Character> map=new HashMap<>(){{
put(')','(');
}} ;
通过哈希表存储其键值对
通过栈的形式防其值
- 如果值没有包括进去,则进栈
- 如果值包括了进去,则要出栈的话(需要判断其键值是否满足栈顶元素以及栈是否为空,因为可能为
(()
,不想此处判断栈是否为空的话,可以在一开始就判断是否为偶数个数) - 单纯的进栈还不可以,因为要键值配对,必须把栈清空,所以最后的栈必须为空,示例如下
代码如下:
class Solution {
public boolean isValid(String s) {
if(s.length()%2==1)return false;
Deque <Character>stack=new LinkedList<>();
Map <Character,Character> map=new HashMap<>(){{
put(')','(');
put('}','{');
put(']','[');
}};
for(char c:s.toCharArray()){
if(map.containsKey(c)){
if(map.get(c)!=stack.peek()){
return false;
}
stack.pop();
}else {
stack.push(c);
}
}
return stack.isEmpty();
}
}
200. 岛屿数量(中等)
题目:
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
示例 2:
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 ‘0’ 或 ‘1’
思路:
通过深度优先遍历,将其遍历过后的数字都变为一个0,也就是状态变量
如果一开始进入的为1,则sum+1
在遍历的时候终止条件,一般有临界值,以及状态值
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int m=grid.length;
int n=grid[0].length;
int sum=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'){
sum++;
dfs(grid,i,j);
}
}
}
return sum;
}
public void dfs(char [][] grid,int i,int j){
int m=grid.length;
int n=grid[0].length;
if(i<0||i>=m||j<0||j>=n||grid[i][j]=='0')return ;
grid[i][j]='0';
dfs(grid,i-1,j);
dfs(grid,i+1,j);
dfs(grid,i,j-1);
dfs(grid,i,j+1);
}
}
上面的思路是修改了原数组,如果不想修改原数组,增加一个状态变量的数组,具体代码如下:(核心代码如下)
public int numIslands(char[][] grid) {
int numIsland = 0;
int[][] visited = new int[grid.length][grid[0].length];
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1' && visited[i][j]!=1){
search(grid, i, j, visited);
numIsland ++;
}
}
}
return numIsland;
}
public void search(char[][] grid, int i, int j, int[][] visited){
if(i<0 || j<0 || i>grid.length-1 || j>grid[0].length-1 || grid[i][j]=='0'|| visited[i][j] == 1) return ;
visited[i][j] = 1;
search(grid, i - 1, j, visited);
search(grid, i , j-1, visited);
search(grid, i + 1, j, visited);
search(grid, i, j + 1, visited);
}
第九天
105. 从前序与中序遍历序列构造二叉树(中等)
题目:
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
提示:
1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
preorder 和 inorder 均 无重复 元素
inorder 均出现在 preorder
preorder 保证 为二叉树的前序遍历序列
inorder 保证 为二叉树的中序遍历序列
思路一:
使用递归的思路,因为先序遍历的第一个头节点是关键,因为他是根节点,与中序遍历的根节点相同,中序遍历的左子树,以及右子树就可区分,以此类推,就可找到其构建的二叉树
- 新建一个二叉树,要先创建一个根节点(找到中序遍历的根节点,然后一个个创建),通过
TreeNode root=new TreeNode(preorder[preorder_left]);
- 每次都要先序遍历的根节点,在遍历中序遍历找到其根节点,复杂度变高了,可以通过set集合存储key value值
- 注意递归的终止条件是先序遍历,只要前面的节点大于后面的节点,输出为null
- 另外一个注意事项是,先序遍历和后序遍历的长度大小是一样的
/**
* 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 {
Map<Integer,Integer>map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map=new HashMap<>();
int n=inorder.length;
for(int i=0;i<n;i++){
map.put(inorder[i],i);
}
return mybuildTree(preorder,inorder,0,n-1,0,n-1);
}
public TreeNode mybuildTree(int []preorder,int [] inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right ){
if(preorder_left>preorder_right)return null;
int inorder_root=map.get(preorder[preorder_left]);
int sum=inorder_root-inorder_left;
TreeNode root=new TreeNode(preorder[preorder_left]);
root.left=mybuildTree(preorder,inorder,preorder_left+1,preorder_left+sum, inorder_left,inorder_root-1);
root.right=mybuildTree(preorder,inorder,preorder_left+sum+1,preorder_right, inorder_root+1,inorder_right);
return root;
}
}
103. 二叉树的锯齿形层序遍历(中等)
题目:
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
示例 1:
示例 2:
示例 3:
提示:
树中节点数目在范围 [0, 2000] 内
-100 <= Node.val <= 100
思路:
通过层次遍历,在内部的列表中使用的是双端队列,如果存储1 23 的时候,通过存放为1 32.因为是双端队列,32 存放的时候是通过offerFirst进行存储
因为是双端队列,所以在添加的时候要强转list.add(new LinkedList<Integer>(sonlist));
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> list=new ArrayList<List<Integer>>();
if(root==null)return list;
Queue<TreeNode> que =new LinkedList<>();
que.offer(root);
boolean direction = true;
while(!que.isEmpty()){
Deque<Integer> sonlist=new LinkedList<>();
int n=que.size();
for(int i=0;i<n;i++){
TreeNode node=que.poll();
if(direction){
sonlist.offerLast(node.val);
}else{
sonlist.offerFirst(node.val);
}
if(node.left!=null)que.offer(node.left);
if(node.right!=null)que.offer(node.right);
}
direction=!direction;
list.add(new LinkedList<Integer>(sonlist));
}
return list;
}
}
另一种思路是不使用双端队列
直接使用列表的形式进行添加
特殊行使用Collection进行反转
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> list=new ArrayList<List<Integer>>();
if(root==null)return list;
Queue<TreeNode> que =new LinkedList<>();
que.offer(root);
boolean direction = true;
while(!que.isEmpty()){
List<Integer> sonlist=new ArrayList<>();
int n=que.size();
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);
}
if(!direction){
Collections.reverse(sonlist);
}
direction=!direction;
list.add(sonlist);
}
return list;
}
}
第十天
94. 二叉树的中序遍历(简单)
题目:
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
示例 2:
示例 3:
示例 4:
示例 5:
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
思路一:
通过递归 的方式
创建一个函数,将其res列表传入
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
}
思路二:
通过迭代的方式进行
利用栈的思想
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stk = new Stack<TreeNode>();
while (root != null || !stk.isEmpty()) {
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
102. 二叉树的层序遍历(中等)
题目:
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
示例 2:
示例 3:
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
思路:
通过列表以及队列的方式进行存储
具体大条件是判断其队列不为空
内部存储的是每一层的列表size
注意泛型的类型
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>>list=new ArrayList<>();
if(root==null)return list;
Queue<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> sonlist=new ArrayList<>();
int n=que.size();
for(int i=1;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;
}
}
394. 字符串解码(中等)
题目:
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
示例 2:
示例 3:
示例 4:
提示:
- 1 <= s.length <= 30
- s 由小写英文字母、数字和方括号 ‘[]’ 组成
- s 保证是一个 有效 的输入。
- s 中所有整数的取值范围为 [1, 300]
思路:
通过栈的存储,以及StringBulider的存储
遇到】的时候都进栈
错误代码展示
class Solution {
public String decodeString(String s) {
Stack<Character>stack=new Stack<>();
StringBuilder res=new StringBuilder();
for(char c:s.toCharArray()){
if(c!=']'){
stack.push(c);
}else{
StringBuilder ans=new StringBuilder();
while(!stack.isEmpty()&&Character.isLetter(stack.peek()))
ans.insert(0,stack.pop());
String sub=ans.toString();
stack.pop();
ans = new StringBuilder();
while(!stack.isEmpty()&&Character.isDigit(stack.peek()))
ans.insert(0,stack.pop());
int count=Integer.valueOf(ans.toString());
while(count>0){
for(char h:sub.toCharArray()){
res.append(h);
}
count--;
}
}
}
return res.toString();
}
}
少考虑了这种情况:
对此在前面的时候不着急直接输出添加进去
而是继续放到栈中,等全部放到栈中之后在一一添加进去
正确代码如下:
- 判断是否为字符
Character.isLetter(stack.peek()
- 或者是否为数字
Character.isDigit(stack.peek()
- 将其栈从后往前的输出,可以通过insert 无限插入到第0个位置
ans.insert(0,stack.pop());
- 字符串转换为数字
Integer.valueOf();
- SrtingBuilder 输出的结构都要通过
toString();
先转换为字符串
class Solution {
public String decodeString(String s) {
Stack<Character>stack=new Stack<>();
StringBuilder res=new StringBuilder();
for(char c:s.toCharArray()){
if(c!=']'){
stack.push(c);
}else{
StringBuilder ans=new StringBuilder();
while(!stack.isEmpty()&&Character.isLetter(stack.peek()))
ans.insert(0,stack.pop());
String sub=ans.toString();
stack.pop();
ans = new StringBuilder();
while(!stack.isEmpty()&&Character.isDigit(stack.peek()))
ans.insert(0,stack.pop());
int count=Integer.valueOf(ans.toString());
while(count>0){
for(char h:sub.toCharArray()){
stack.push(h);
}
count--;
}
}
}
while(!stack.isEmpty()){
res.insert(0,stack.pop());
}
return res.toString();
}
}
第十一天
415. 字符串相加(简单)
题目:
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
示例 1:
示例 2:
示例 3:
提示:
1 <= num1.length, num2.length <= 104
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零
思路:
通过模拟的思路,将其两个字符串从后往前加上,如果有进位就保留进位
通过StringBuilder添加进入,但是记得要reverse
反转,之后还要将其输出为字符串toString();
class Solution {
public String addStrings(String num1, String num2) {
int i=num1.length()-1,j=num2.length()-1;
int add=0;
StringBuilder ans=new StringBuilder();
while(i>=0||j>=0||add!=0){
int x=i>=0?num1.charAt(i)-'0':0;
int y=j>=0?num2.charAt(j)-'0':0;
int result=x+y+add;
ans.append(result%10);
add=result/10;
i--;
j--;
}
ans.reverse();
return ans.toString();
}
}