数学 > 数组 > 链表 > 字符串 > 哈希表 > 双指针 > 递归 > 栈 > 队列 > 树
数组_中等
209_长度最小的子数组
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动串口,(其实还是双指针)
//拿符合的子串的长度与后面的符合的子串长度做对比,找出长度最小的那个
//不会漏掉任何一个符合的子串
int start = 0;
int end = 0;
int sum = 0;
int minLen = nums.length+1;
while(end<nums.length&&start<nums.length){
//end和start变化后与minlen的记录的原子性操作很重要
sum += nums[end];
while (sum >= target) {
minLen = Math.min(minLen,end-start+1);
sum -= nums[start++];
}
end++;
}
return minLen == nums.length+1? 0:minLen;
}
}
59. 螺旋矩阵 II
跳转锚点
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int edgeLen = 0;//本圈距离边缘的距离
int i = 0;//行数
int j = 0;//列数
int num = 1;//填入的数字
//按层模拟,我这种有点像是模拟和按层模拟的结合
//感觉还是'拿了橘子跑啊'的思路好一点,不断收缩left\right\bottom\top
for (; edgeLen < Math.ceil(((double)n)/2) ; edgeLen++) {
while (j<n-edgeLen) {
matrix[i][j] = num;
j++;
num++;
}
j--;
i++;
while (i<n-edgeLen) {
matrix[i][j] = num;
i++;
num++;
}
i--;
j--;
while (j>=0+edgeLen) {
matrix[i][j] = num;
j--;
num++;
}
j++;
i--;
while (i>=0+edgeLen+1) {
matrix[i][j] = num;
i--;
num++;
}
i++;
j++;
}
return matrix;
}
}
238_除自身以外数组的乘积
class Solution {
public int[] productExceptSelf(int[] nums) {
//暴力解法是O(n2)
//不能用除法,就不能乘一块再除
//ans不过就是左前缀乘积和右前缀乘积
//ansL[i] = ans[i-1] * nums[i-1]
//ansR[i] = ans[i+1] * nums[i+1]
//ans[i] = ansL[i] * ansR[i]
//左遍历一次,右遍历一次
//时间复杂度O(n),空间复杂度O(n),额外用了一个数组
int[] ansL = new int[nums.length];
int[] ansR = new int[nums.length];
ansL[0] = ansR[nums.length-1] = 1;
for (int i = 1; i < nums.length; i++) {
ansL[i] = ansL[i - 1] * nums[i - 1];
}
for (int j = nums.length - 2; j > -1; j--) {
ansR[j] = ansR[j + 1] * nums[j + 1];
}
for (int i = 0; i < nums.length; i++) {
ansR[i] = ansL[i] * ansR[i];
}
return ansR;
}
}
56_合并区间
class Solution {
public int[][] merge(int[][] intervals) {
// 排序
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] o1,int[] o2){
return o1[0] - o2[0];
}
});
List<int[]> mergeIntervals = new ArrayList<>();
//int[]不是基本数据类型,可以做泛型
mergeIntervals.add(intervals[0]);
// 遍历剩余区间
for (int i = 1; i < intervals.length; i++) {
int[] lastInterval = mergeIntervals.get(mergeIntervals.size() - 1);
//最后一个合并区间
if (intervals[i][0] <= lastInterval[1]) {
// 重叠,更新结束位置
lastInterval[1] = Math.max(lastInterval[1], intervals[i][1]);
} else {
// 不重叠,添加新区间
mergeIntervals.add(intervals[i]);
}
}
return mergeIntervals.toArray(new int[0][]);
//转换为int[][],因为默认是object[]
//int[]是object的子类,但不是object[]的子类
//为什么int[0][]必须有这个零
//如果传入的数组长度 等于或大于 List 的元素数量(merged.size()),
//直接将 List 的元素填充到这个数组中,并返回它。
//如果传入的数组长度 小于 List 的元素数量,会忽略传入的数组,
//创建一个新数组(大小为 List 的元素数量),然后填充并返回。
//用new int[merged.size()][]也可以
}
}
189_轮转数组
class Solution {
public void rotate(int[] nums, int k) {
//尾部的k个元素会被移动到最前面
//其余元素向后移动k
//当热如果k大于nums.length,应该是移动了k mod n个位置多于一圈,算不到一圈的余量
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k % nums.length - 1);
reverse(nums, k % nums.length, nums.length - 1);
}
public void reverse(int[] nums, int start, int end){
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
链表_中等
707_设计链表
class MyNode {
Integer val;
MyNode next;
public MyNode(Integer val){
// 注意这里
//坑死我了,我淦
//必须加this指明是成员变量,不加this优先表示的是成员变量val自己给自己传值
this.val = val;
next = null;
}
public MyNode(Integer val,MyNode ptr){
this.val = val;
next = ptr;
}
}
class MyLinkedList {
//头节点的定义将会省很多麻烦,很重要
MyNode head;
//MyNode ptr; ptr的声明不能放在这,将会在堆中存储,当多个线程调用一个new MyLinkedList就会重复覆盖引用
public MyLinkedList() {
head = new MyNode(0);
}
public int get(int index) {
MyNode ptr;//在方法中声明就是局部变量,在各线程的栈中存储
ptr = head;
for (int i = 0; i < index+1; i++) {
ptr = ptr.next;
if(ptr==null){
return -1;
}
}
return ptr.val;
}
public void addAtHead(int val) {
MyNode ptr;
ptr = new MyNode(val);
ptr.next = head.next;
head.next = ptr;
}
public void addAtTail(int val) {
MyNode ptr;
ptr = head;
while (ptr !=null && ptr.next != null) {
ptr = ptr.next;
}
ptr.next = new MyNode(val);
}
public void addAtIndex(int index, int val) {
MyNode ptr;
ptr = head;
for (int i = 0; i < index; i++) {//获取idx-1位置的节点
ptr = ptr.next;
if(ptr == null){
return;
}
}
ptr.next = new MyNode(val,ptr.next);
}
public void deleteAtIndex(int index) {
MyNode ptr;
ptr = head;
for (int i = 0; i < index; i++) {//获取idx-1位置的节点
ptr = ptr.next;
if(ptr == null){
return;
}
}
//ptr.next才是索引的那个节点,必须保证不能为null
if(ptr == null || ptr.next == null) return;
ptr.next = ptr.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
24_两两交换链表中的节点
/**
* 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 swapPairs(ListNode head) {
int len = 0;
ListNode newHead = new ListNode(0);
newHead.next = head;
ListNode ptr = newHead;
while (ptr.next !=null && ptr.next.next !=null) {
//链表的题的精髓就是每一轮一个固定的变量代表一个固定的节点
//这样思路比较清晰
//然后随着轮,迭代变量代表的节点
ListNode front = ptr.next;
ListNode back = ptr.next.next;
ptr.next = back;
ListNode temp = front.next;
front.next = back.next;
back.next = front;
ptr = front;
}
return newHead.next;
}
}
19_删除链表的倒数第 N 个结点
记录链表长度
/**
* 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 removeNthFromEnd(ListNode head, int n) {
//特殊情况[],[1]
//还是先建立一个头节点作为空节点
//做链表的题,一定要节点化,思路就清晰
if (head == null) {
return head;
}
ListNode newHead = new ListNode(0);
newHead.next = head;
ListNode ptr = head;
Integer len = 0;
while (ptr!=null) {
ptr = ptr.next;
len++;
}
ptr = newHead;
for (int i = 0; i < len-n+1-1; i++) {
ptr = ptr.next;
}
//ptr现在是要被删除的节点前面那个
ListNode front = ptr;
ListNode delNode = ptr.next;
ListNode back = ptr.next.next;
front.next = back;
return newHead.next;
}
//还有一种双指针的方法
}
基于递归或栈的方法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//栈,记录退栈的次数
//还是要在头部定义一个新节点,能解决很多空指针的问题
ListNode newHead = new ListNode(0);
newHead.next = head;
Deque<ListNode> stack = new LinkedList<>();
ListNode ptr = newHead;
while (ptr != null) {
stack.push(ptr);
ptr = ptr.next;
}
for (int i = 0; i < n; i++) {
stack.pop();
}
ptr = stack.peek();
ptr.next = ptr.next.next;
return newHead.next;
}
}
142_环形链表 II
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
//这道题就是如何找链表的头节点,芜湖
//先说结论,用快慢指针
//当快慢指针相遇后,快指针回到头节点,这回快慢指针都只走一步,再次相遇时
//那个节点就是头节点
//设从头节点到环入口的距离为a
//从环入口到相遇点的距离为b
//从相遇点回到环入口的距离为c
//那么从开始走到第一次相遇,快指针走的步数是慢节点的两倍
//则,得:2(a+b)=a+b+c+b
//得,a=c
//所以,快指针再从头开始走到环入口(a的距离)就和慢指针相遇(c的距离)
ListNode fastPtr = head;
ListNode slowPtr = head;
while (true) {
if (fastPtr ==null || fastPtr.next ==null) {
return null;
}
fastPtr = fastPtr.next.next;
slowPtr = slowPtr.next;
if (fastPtr == slowPtr) {
fastPtr = head;
break;
}
}
while (true) {
if (fastPtr == slowPtr) {
return fastPtr;
}
fastPtr = fastPtr.next;
slowPtr = slowPtr.next;
}
//其他比较慢的方法就是,空间复杂度高的方法就是哈希了
}
}
138_随机链表的复制
/*
// 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;
}
Node ptr = head;
while (ptr != null) {
Node newNode = new Node(0);
//对复制节点赋值
newNode.val = ptr.val;
//复制节点保存原节点的next节点
newNode.next = ptr.next;
ptr.next = newNode;
//指向下个节点
ptr = newNode.next;
}
ptr = head;
// 随机指向变为深拷贝
while (ptr != null) {
if (ptr.random !=null) {
ptr.next.random = ptr.random.next;
}
else{
ptr.next.random = null;
}
ptr = ptr.next.next;
}
// 分开原链表和新链表
ptr = head;
Node newHead = ptr.next;
Node newPtr = newHead;
while (ptr !=null) {
ptr.next = ptr.next.next;
if (newPtr.next != null) {
newPtr.next = newPtr.next.next;
}
ptr = ptr.next;
newPtr = newPtr.next;
}
return newHead;
}
}
2_两数相加
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
ListNode ptrL1 = l1;
ListNode ptrL2 = l2;
ListNode newHead = new ListNode(0);
ListNode newPtr = newHead;
int l1Num = 0;
int l2Num = 0;
int newNum = 0;
int carryFlag = 0;
// 只要两个相加的链表或进位不为0就要一直创建新节点承接值
while (ptrL1 != null || ptrL2 != null || carryFlag != 0) {
l1Num = ptrL1 == null? 0 : ptrL1.val;
l2Num = ptrL2 == null? 0 : ptrL2.val;
newNum = (l1Num + l2Num + carryFlag) % 10;
newPtr.next = new ListNode(newNum);
carryFlag = (l1Num + l2Num + carryFlag) / 10;
newPtr = newPtr.next;
if (ptrL1 != null) {
ptrL1 = ptrL1.next;
}
if (ptrL2 != null) {
ptrL2 = ptrL2.next;
}
}
return newHead.next;
}
}
146_LRU 缓存
class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
private Map<Integer, DLinkedNode> cache = 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 = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
} else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(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 void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
直接用LinkedHashMap
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache extends LinkedHashMap<Integer, Integer> {
private final int capacity;
// 构造函数
public LRUCache(int capacity) {
// initialCapacity: 初始容量
// loadFactor: 加载因子
// accessOrder: true 表示按访问顺序排序
super(capacity, 0.75f, true);
this.capacity = capacity;
}
// 获取元素
public int get(int key) {
return super.getOrDefault(key, -1); // 如果不存在返回 -1
}
// 插入或更新元素
public void put(int key, int value) {
super.put(key, value);
}
// 重写 removeEldestEntry 方法,控制缓存大小
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
// 当元素数量超过容量时,移除最老的元素(最近最少使用的)
return size() > capacity;
}
}
字符串_中等
151_反转字符串中的单词
class Solution {
public String reverseWords(String s) {
//string是一种特殊的不可变的数组,所以如果某个函数的传参是数组.string可以作为形参
StringBuffer stringBuffer = new StringBuffer(s).reverse();
//保证在处理每一个词的时候都有上升沿和下降沿
stringBuffer.insert(0, ' ');
stringBuffer.append(' ');
int left = 0;
int right = 0;
char leftChar;
char rightChar;
for (int i = 1; i < stringBuffer.length(); i++) {
if (stringBuffer.charAt(i-1) == ' ' &&
stringBuffer.charAt(i) != ' ') {
left = i;
}
if (stringBuffer.charAt(i-1) != ' ' &&
stringBuffer.charAt(i) == ' ') {
right = i-1;
while (left<right) {
leftChar = stringBuffer.charAt(left);
rightChar = stringBuffer.charAt(right);
stringBuffer.setCharAt(left, rightChar);
stringBuffer.setCharAt(right, leftChar);
left++;
right--;
}
}
if (stringBuffer.charAt(i-1) == ' ' &&
stringBuffer.charAt(i) == ' ') {
stringBuffer.deleteCharAt(i);
i--;
}
}
return stringBuffer.toString().trim();
}
// public void createObject() {
// // 在方法中创建一个对象
// MyClass obj = new MyClass();
// System.out.println("Object created: " + obj);
// }
//局部的对象被清理是引用MyClass的变量obj存在栈中,方法运行完,obj被销毁,没有引用MyClass()
//的变量,这个对象就被清理
}
438_找到字符串中所有字母异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
//用两个数组分别存储s滑动窗口内元素的数量,和p中的各元素的数量
//这里是用数组还是hashMap
//hashMap的初始容量为16,考虑到a-z字符在hashMap中存储还是比较均匀的
//当hashMap中键值对大于16*0.75才会扩容到32
//所以如果滑动窗口内不同元素或p中的不同元素大于16*0.75,肯定是数组好,因为hashMap肯定要遍历空桶
//键值对小于16*0.75(存储还是比较均匀的)大概率hashMap更好一些
//折中一下,还是用数组吧
//每次并不需要比较所有的s滑动窗口内元素的数量和p中的各元素的数量
//用一个differ差异数量来记录差异
//单独处理p串长于s串的情况
if (s.length() < p.length()) {
return new ArrayList<Integer>();
}
List<Integer> list = new ArrayList<>();
int[] count = new int[26];
int differ = 0;
for (int i = 0; i < p.length(); i++) {
--count[p.charAt(i) - 'a'];
++count[s.charAt(i) - 'a'];
}
for (int i = 0; i < count.length; i++) {
if(count[i] != 0){
differ++;
}
}
if (differ == 0) {
list.add(0);
}
int preSub, aftSub, preAdd, aftAdd;
for (int i = 0; i < s.length() - p.length(); i++) {
if (count[s.charAt(i) - 'a'] == 0) {
preSub = 0;
}
else{
preSub = 1;
}
if(count[s.charAt(i + p.length()) - 'a'] == 0){
preAdd = 0;
}
else{
preAdd = 1;
}
count[s.charAt(i) - 'a'] -= 1;
count[s.charAt(i + p.length()) - 'a'] += 1;
if (count[s.charAt(i) - 'a'] == 0) {
aftSub = 0;
}
else{
aftSub = 1;
}
if(count[s.charAt(i + p.length()) - 'a'] == 0){
aftAdd = 0;
}
else{
aftAdd = 1;
}
if (preSub == 0 && aftSub == 1) {
differ++;
}
else if (preSub == 1 && aftSub == 0) {
differ--;
}
if (preAdd == 0 && aftAdd == 1) {
differ++;
}
else if(preAdd == 1 && aftAdd == 0){
differ--;
}
if (differ == 0) {
list.add(i + 1);
}
}
return list;
}
}
哈希表
454_四数相加 II
import java.util.Map.Entry;
import java.util.Map.Entry;class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//数的相加,用哈希就可以保存数值,就不用遍历了,省时间复杂度
//但是会增加空间复杂度
//AB数组为一组,CD数组为一组
//将AB组合的所有组合添加在一个哈希中,CD同样
//建为数组,值为出现的次数
Map<Integer,Integer> abHashMap = new HashMap<>();
Map<Integer,Integer> cdHashMap = new HashMap<>();
int count = 0;
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
//getOrDefault如何哈希中存在这个键,返回对应值
//没有,返回默认值
abHashMap.put(nums1[i]+nums2[j],
abHashMap.getOrDefault(nums1[i]+nums2[j], 0)+1);
}
}
for (int i = 0; i < nums3.length; i++) {
for (int j = 0; j < nums4.length; j++) {
cdHashMap.put(nums3[i]+nums4[j],
cdHashMap.getOrDefault(nums3[i]+nums4[j],0)+1);
}
}
for (Entry<Integer,Integer> entrySet : abHashMap.entrySet()) {
if (cdHashMap.containsKey(0-entrySet.getKey())) {
count +=entrySet.getValue()*cdHashMap.get(0-entrySet.getKey());
}
}
return count;
}
}
49_字母异位词分组
import java.util.Map.Entry;
import java.util.Map.Entry;class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> out = new LinkedList<>();
Map<String,List<String>> hashMap = new HashMap<>();
//踩坑,数组其实继承的是object类,所以数组的toString方法没重写过,是默认输出
//类名@哈希值
for(String str : strs){
char[] charArray = str.toCharArray();
Arrays.sort(charArray);
String key = new String(charArray);
List<String> value = hashMap.getOrDefault(key, new LinkedList<String>());
value.add(str);
hashMap.put(key, value);
}
for(Entry<String,List<String>> entry : hashMap.entrySet()){
out.add(entry.getValue());
}
return out;
}
}
128_最长连续序列
class Solution {
public int longestConsecutive(int[] nums) {
//时间复杂度O(n)意味不能用排序
//哈希表
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
// 遍历每一个数,当这个数不是连续数列的头数就跳过,在头数的内循环中会遍历这个数
//所以总的来说是O(n)
int maxLen = 0;
for(Integer num : set){//用set遍历会快一些,因为set去重了
if (set.contains(num - 1)) {
continue;
}
int currentMaxLen = 1;
int currentNum = num;
while (set.contains(++currentNum)) {
currentMaxLen++;
maxLen = Math.max(currentMaxLen, maxLen);
}
maxLen = Math.max(currentMaxLen, maxLen);
}
return maxLen;
}
}
双指针
15_三数之和
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//排序+双指针
//这道题也可以用哈希,但是在空间复杂度上要高一些
Arrays.sort(nums);
List<List<Integer>> list = new LinkedList<>();
//固定外层循环,内层循环一个指向固定外层索引的下一个,一个指向最后一位
//三个条件
//i != j、i != k 且 j != k
//nums[i] + nums[j] + nums[k] == 0
//答案中不可以包含重复的三元组。
for (int i = 0; i < nums.length-2; i++) {
if (i>0 && nums[i]==nums[i-1]) {
continue;
}
int left = i+1;
int right = nums.length-1;
while (left<right) {
if (nums[i]+nums[left]+nums[right]>0) {
right--;
}
else if(nums[i]+nums[left]+nums[right]<0){
left++;
}
else{
List<Integer> group = new LinkedList<>();
group.add(nums[i]);
group.add(nums[left]);
group.add(nums[right]);
list.add(group);
left++;
right--;
while (left<right&& nums[left]==nums[left-1]) {
left++;
}
while (left<right&& nums[right]==nums[right+1]) {
right--;
}
}
}
}
return list;
}
}
18_四数之和
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new LinkedList<>();
int left;
int right;
Arrays.sort(nums);
//延续三数之和的思路
//我还以为有什么高明的解法呢,结果还是排序+双指针,时间复杂度为O(n^3)
for (int i = 0; i < nums.length-3; i++) {
//防止前后两数相等
while (i>0 && i < nums.length-3 && nums[i-1]==nums[i]) {
i++;
}
for (int j = i+1; j < nums.length-2; j++) {
//防止前后两个数相等
while (j>i+1 && j < nums.length-2 && nums[j-1]==nums[j]) {
j++;
}
left = j+1;
right = nums.length-1;
while (left<right) {
//群众里有坏人,相加后用int会溢出
long sum = (long)nums[i]+nums[j]+nums[left]+nums[right];
if (sum>target) {
right--;
}
else if(sum<target){
left++;
}
else{
List<Integer> group = new LinkedList<>();
group.add(nums[i]);
group.add(nums[j]);
group.add(nums[left]);
group.add(nums[right]);
list.add(group);
left++;
right--;
//这里很关键
//为什么之前不等于的时候,前后相等不需要再移动指针?
//这主要是防止将相同数值元组添加到列表中,
//前面没有添加操作,不需要移动,让他慢慢移动就行了
while (left<right && nums[left] == nums[left-1]) {
left++;
}
while (left<right && nums[right] == nums[right+1]) {
right--;
}
}
}
}
}
return list;
}
}
5_最长回文子串
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() == 0 || s.length() == 1) {
return s;
}
int len = s.length();
int maxSonLen = 0;
int maxSonLenL = 0;//最长子串左边界
int maxSonLenR =0;//最长子串右边界
//先找重叠扩散,找adsda这种的
for (int i = 0; i < len; i++) {
int l = i;
int r = i;
while (l >= 0 && r <= len-1) {
if (s.charAt(l) != s.charAt(r)) {
break;
}
if (maxSonLen < (r-l+1)) {
maxSonLen = r-l+1;
maxSonLenL = l;
maxSonLenR = r;
}
l--;
r++;
}
}
//再找不重叠扩散,找baab这种
for (int i = 0; i < len; i++) {
int l = i;
int r = i+1;
while (l >= 0 && r <= len-1) {
if (s.charAt(l) != s.charAt(r)) {
break;
}
if (maxSonLen < (r-l+1)) {
maxSonLen = r-l+1;
maxSonLenL = l;
maxSonLenR = r;
}
l--;
r++;
}
}
StringBuilder sb = new StringBuilder();
sb.append(s, maxSonLenL, maxSonLenR + 1);
return sb.toString();
}
}
11_盛最多水的容器
class Solution {
public int maxArea(int[] height) {
//开始头尾指针作为边界
//如果开始h[l]和h[r],h[l]更小,那么现在无论h[r]怎么移动,当前都是以
//h[l]为边界的最大值了,所以移动i这个指针继续寻找
//area = min(h[l],h[r])*(r-l)
int l = 0;
int r = height.length-1;
int maxArea = Math.min(height[l], height[r]) * (r-l);
while (l < r) {
maxArea = Math.max(maxArea, (Math.min(height[l], height[r]) * (r-l)));
if (height[l] < height[r]) {
l++;
} else {
r--;
}
}
return maxArea;
}
}
3_无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> hashMap= new HashMap<>();
int maxLen = 0;
int conCurrentLen = 0;
Integer idx = -1;
// 右指针探索,左指针根据情况紧缩
for (int i = 0; i < s.length(); i++) {
if (hashMap.containsKey(s.charAt(i))) {
idx = hashMap.get(s.charAt(i)) > idx ?
hashMap.get(s.charAt(i)) : idx;
conCurrentLen = i - idx;
}
else{
conCurrentLen++;
}
maxLen = maxLen > conCurrentLen ? maxLen : conCurrentLen;
hashMap.put(s.charAt(i), i);
}
return maxLen > conCurrentLen ? maxLen : conCurrentLen;
}
}
75_颜色分类
class Solution {
public void sortColors(int[] nums) {
//两个关键点
//双指针可以实现1次遍历
//指针p0交换0,指针p1交换1
//当p0<p1时,p0交换出去的肯定是1,所以需要用指针p1把这个1交换过来
//所以当指针p0交换0时,指针p0和指针p1都要往后移
int p0 = 0;
int p1 = 0;
int temp;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) {
temp = nums[i];
nums[i] = nums[p0];
nums[p0] = temp;
if (p0 < p1) {
temp = nums[i];
nums[i] = nums[p1];
nums[p1] = temp;
}
p0++;
p1++;
}
else if(nums[i] == 1){
temp = nums[i];
nums[i] = nums[p1];
nums[p1] = temp;
p1++;
}
}
}
}
287_ 寻找重复数
class Solution {
public int findDuplicate(int[] nums) {
//这题必须记忆一点,根据题目的限制,必定成环,且环的入口的下标就是重复数
int slow = 0;
int fast = 0;
do{
slow = nums[slow];
fast = nums[nums[fast]];
}while(slow !=fast);
//相遇了
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
//自环不会影响此题的解答,初始在idx0处,而题目指出数值在[1,n],idx0处不会自环
//而其他只有一个的数值自环的话,路径根本到不了那里
}
堆_中等
215_数组中的第K个最大元素
手搓最小堆的方法
class Solution {
public int findKthLargest(int[] nums, int k) {
//快速排序的选择方法,时间复杂度O(n),空间复杂度O(logn)
//最小堆,时间O(n + klogn),空间O(logn)
//和最小栈区分开来
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
swap(nums, 0, i);
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a,int heapSize){
for (int i = heapSize / 2 - 1; i >= 0; i--) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a,int i, int heapSize){
int l = i * 2 + 1, r = i * 2 + 2,largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest, heapSize);
}
}
public void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
使用java定义的最小堆PriorityQueue
import java.util.*;
class Solution {
public int findKthLargest(int[] nums, int k) {
// 使用一个含有 k 个元素的最小堆,PriorityQueue 底层是动态数组,为了防止数组扩容产生消耗,可以先指定数组的长度
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, Comparator.comparingInt(a -> a));
//最大堆
// PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Comparator.comparingInt(a -> -a));
for (int i = 0; i < k; i++) {
minHeap.offer(nums[i]);
}
int n = nums.length;
for (int i = k; i < n; i++) {
if(nums[i] > minHeap.peek()){
minHeap.poll();
minHeap.offer(nums[i]);
}
}
return minHeap.peek();
}
}
347_前 K 个高频元素
import java.util.Map.Entry;
import java.util.Map.Entry;class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> occurrences = new HashMap<>();
for(int num : nums){
occurrences.put(num, occurrences.getOrDefault(num, 0)+1);
}
//小顶堆数据结构(优先队列)
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
public int compare(int[] m, int[] n){
return m[1]-n[1];
}
});
for (Entry<Integer,Integer> entry : occurrences.entrySet()) {
int num = entry.getKey() , count = entry.getValue();
if (queue.size() == k){
if (queue.peek()[1] < count){
queue.poll();
queue.offer(new int[]{num,count});
}
}
else{
queue.offer(new int[]{num,count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
更优雅的解法
import java.util.Map.Entry;
import java.util.Map.Entry;class Solution {
public int[] topKFrequent(int[] nums, int k) {
//哈希 + 最小堆
Map<Integer,Integer> map = new HashMap<>();
//小顶堆
PriorityQueue<Entry<Integer,Integer>> minHeap =
new PriorityQueue<>((a,b) -> a.getValue() - b.getValue());
for (int i = 0; i < nums.length; i++) {
Integer count = map.getOrDefault(nums[i], 0);
map.put(nums[i], ++count);
}
for (Entry<Integer,Integer> entrySet : map.entrySet()) {
if (minHeap.size() == k) {
if (entrySet.getValue() > minHeap.peek().getValue()) {
minHeap.poll();
minHeap.offer(entrySet);
}
}
else{
minHeap.offer(entrySet);
}
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = minHeap.poll().getKey();
}
return res;
}
}
树_中等
102_二叉树的层序遍历
/**
* 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 LinkedList<>();
//一定要考虑空树的情况
if (root==null) {
return list;
}
//额外使用的空间是队列queue和listOneLayer存储每层数值
//将每层的节点存入队列
//取节点的时候也是一次批量取节点
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> listOneLayer = new LinkedList<>();
int len = queue.size();
while (len>0) {
TreeNode treeNode = queue.poll();
listOneLayer.add(treeNode.val);
if (treeNode.left!=null) {
queue.offer(treeNode.left);
}
if (treeNode.right!=null) {
queue.offer(treeNode.right);
}
len--;
}
list.add(listOneLayer);
}
return list;
}
}
107_二叉树的层序遍历 II
/**
* 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>> levelOrderBottom(TreeNode root) {
//层序遍历,广度优先搜素,用队列的思想
//与二叉树的层序遍历I的区别只是添加到list的顺序变了
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> list = new LinkedList<>();
queue.offer(root);
if (root==null) {
return list;
}
while (!queue.isEmpty()) {
int lenOneLayer = queue.size();
List<Integer> listOneLayer = new LinkedList<>();
for (int i = 0; i < lenOneLayer; i++) {
TreeNode ptr = queue.poll();
if (ptr.left != null) {
queue.offer(ptr.left);
}
if (ptr.right != null) {
queue.offer(ptr.right);
}
listOneLayer.add(ptr.val);
}
list.add(0,listOneLayer);
}
return list;
}
}
199_二叉树的右视图
/**
* 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) {
//就是每一层最右边那个
//深度优先搜索不能知道每个节点是哪层的,
//只有广度优先搜索可以
Deque<TreeNode> deque = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if (root == null) {
return list;
}
TreeNode ptr = root;
deque.offer(root);
while (!deque.isEmpty()) {
int count = deque.size();//计数每层节点数
while (count>0) {
ptr = deque.poll();
if (count==1) {
list.add(ptr.val);
}
if (ptr!=null&&ptr.left!=null) {
deque.offer(ptr.left);
}
if (ptr!=null&&ptr.right!=null) {
deque.offer(ptr.right);
}
count--;
}
}
return list;
}
}
98_验证二叉搜索树
/**
* 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 {
//直接用中序遍历,如果是递增的,那么一定是二叉搜索树
//中序遍历要么用栈结构实现,要么用递归这种底层栈帧实现
long currentVal = -Long.MAX_VALUE;
public boolean isValidBST(TreeNode root) {
TreeNode ptr = root;
return recursion(ptr);
}
public boolean recursion(TreeNode ptr){
if (ptr == null) {
return true;
}
if (!recursion(ptr.left)) {
return false;
}
if ((long)ptr.val <= currentVal) {
return false;
}
currentVal = ptr.val;
if (!recursion(ptr.right)) {
return false;
}
return true;
}
}
矩阵
73_矩阵置零
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean flagCol0 = false, flagRow0 = false;
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
flagCol0 = true;
}
}
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
flagRow0 = true;
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
if (flagCol0) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
if (flagRow0) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
}
}
54_螺旋矩阵
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
//边界收缩
//i行,j列
//上边界,i,下边界matrix.length-1-i
//左边界,j.右边界matrix[].length-1-i
int upEdge = 0, downEdge = matrix.length - 1;
int leftEdge = 0, rightEdge = matrix[0].length - 1;
List<Integer> list = new ArrayList<>();
while(upEdge <= downEdge && leftEdge <= rightEdge){
//左往右
for (int j = leftEdge; j <= rightEdge; j++) {
list.add(matrix[upEdge][j]);
}
upEdge++;
if (upEdge > downEdge) {
break;
}
//上往下
for (int i = upEdge; i <= downEdge; i++) {
list.add(matrix[i][rightEdge]);
}
rightEdge--;
if (leftEdge > rightEdge) {
break;
}
//右往左
for (int j = rightEdge; j >= leftEdge; j--) {
list.add(matrix[downEdge][j]);
}
downEdge--;
if (upEdge > downEdge) {
break;
}
//下往上
for (int i = downEdge; i >= upEdge; i--) {
list.add(matrix[i][leftEdge]);
}
leftEdge++;
if (leftEdge > rightEdge) {
break;
}
}
return list;
}
}
59_螺旋矩阵||
48_旋转图像
class Solution {
public void rotate(int[][] matrix) {
//需要原地旋转,所以空间复杂度最好为o(1)
//用翻转代替旋转
//先翻转再纵轴对称调换元素值
int mLen = matrix.length;
int nLen = matrix[0].length;
for (int i = 0; i < mLen; i++) {
for (int j = i; j < nLen; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
for (int i = 0; i < mLen; i++) {
for (int j = 0; j < nLen/2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][mLen - 1 - j];
matrix[i][mLen - 1 - j] = temp;
}
}
}
}
240_搜索二维矩阵 II
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//先用二分查找查行再查列复杂度是O(logmlogn)
//每次都能排除这行或这列剩余元素的半数元素
//Z字查找法
//将矩阵逆时针翻转45°,右上角开始遍历,如果比目标值大就j--,反之i++
//每次都能排除这行或这列剩余元素
//最多遍历m+n次
int mLen = matrix.length;
int nLen = matrix[0].length;
int currentVal = matrix[0][nLen - 1];
int currentRow = 0;
int currentCol = nLen - 1;
while (true) {
if (currentCol < 0 || currentRow > mLen - 1) {
return false;
}
currentVal = matrix[currentRow][currentCol];
if (target > currentVal) {
currentRow++;
}
else if (target < currentVal) {
currentCol--;
}
else{
return true;
}
}
}
}
贪心
55_跳跃游戏
class Solution {
public boolean canJump(int[] nums) {
//贪心
//记录遍历时当前最大的idx+跳跃能力
int maxDistance = 0;
int length = nums.length - 1;
for (int i = 0; i < nums.length; i++) {
if (maxDistance < i) {
return false;
}
maxDistance = Math.max(maxDistance, nums[i] + i);
if (maxDistance >= length) {
return true;
}
}
return false;
}
}
45_跳跃游戏 II
class Solution {
public int jump(int[] nums) {
//条件,一定能到达索引n-1处
//distance = step + idx
int footOnLand = 0;
//A点所能到达的所有点中,选出一个maxdistance(B,C,D)
int maxPosition = 0;
int nextMaxPosition = 0;
int step = 0;
if (nums.length == 1) {
return 0;
}
for (int i = 0; i < nums.length;) {
maxPosition = nums[footOnLand] + footOnLand;//当前A点最远距离
while (i <= maxPosition) {//找出maxdistance(B,C,D)
//之前和A都没比过的就不用比了
if (i < nums.length && (nums[i] + i) >= nextMaxPosition) {
nextMaxPosition = nums[i] + i;//迭代下一个点的最大距离
footOnLand = i;//迭代落脚点
}
i++;//遍历数组
}
step++;//步数+1
if (maxPosition >= nums.length -1) {//最远到达或超过最远点跳出返回
break;
}
}
return step;
}
}
139_单词拆分
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
//动态规划&贪心算法
//定义boolean,dp数组存worddict中的串是否满足拼接s中的0:i
//dp[j] = dp[i] && s.substring(i,j)
//目的就是让dp[]的最后一位为true,就说明wordDict满足拼接成s
Set<String> hashSet = new HashSet<>();
for(String word : wordDict){
hashSet.add(word);
}
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for(int i = 0 ; i < s.length() + 1; i++){
for(int j = i + 1; j < s.length() + 1; j++){
if(dp[i] && hashSet.contains(s.substring(i,j))){
dp[j] = true;
}
}
}
return dp[s.length()];
}
}
回溯
46_全排列(真心捋不明白)
class Solution {
public List<List<Integer>> permute(int[] nums) {
// 终止条件:当 cur 的长度等于 nums 的长度时,说明找到一个完整排列,加到 res。
// 循环尝试:每次从 nums 中挑一个没用过的数字加到 cur,递归探索下一步。
// 回溯:递归返回后,移除最后一个数字,尝试其他选择。
// 避免重复:cur.contains(nums[i]) 确保每个数字只用一次。
List<List<Integer>> res = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
backtracking(res, cur, nums);
return res;
}
private static void backtracking(List<List<Integer>> res,
List<Integer> cur, int[] nums) {
// 终止条件
if (cur.size() == nums.length) {
res.add(new ArrayList<>(cur));
}
// 处理逻辑
for (int i = 0; i < nums.length; i++) {
if (!cur.contains(nums[i])) {
cur.add(nums[i]);
backtracking(res, cur, nums);
cur.remove(cur.size() - 1);
}
}
}
}
二分查找
74_搜索二维矩阵
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//二分查找,相当于将二维数组展平为一维数组
int mLen = matrix.length, nLen = matrix[0].length;
int lFlatten = 0, rFlatten = matrix.length * matrix[0].length - 1;
while (lFlatten <= rFlatten) {
int midFlatten = (lFlatten + rFlatten) / 2;
int midFlattenRow = midFlatten / matrix[0].length;
int midFlattenVol = midFlatten % matrix[0].length;
if (matrix[midFlattenRow][midFlattenVol] > target) {
rFlatten = midFlatten - 1;
}
else if(matrix[midFlattenRow][midFlattenVol] < target){
lFlatten = midFlatten + 1;
}
else{
return true;
}
}
return false;
}
}
//二分查找问题的边界一定要精确
//否则跳不出循环
//当索引比较值大于目标值,结果就不可能为索引比较值
//所以将右边界确定为索引比较值小一个索引的值
33. 搜索旋转排序数组
class Solution {
public int search(int[] nums, int target) {
//条件中数组已经排序了,首先想到二分查找
//相当于数组整体向右移动了nums.length - k个位置
//新数组的索引为(idx + nums.length - k)/nums.length
//先要找到k是什么
//二分一次
//肯定是l,mid或者mid+1,r有一个是乱序的
//如果target在乱序中,继续二分乱序
//如果target在排序中,则在排序中查
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[l] <= nums[mid]) {
if (target >= nums[l] && target < nums[mid]) {
r = mid - 1;
}
else{
l = mid + 1;
}
}
else if(nums[mid] <= nums[r]){
if (target > nums[mid] && target <= nums[r]) {
l = mid + 1;
}
else{
r = mid - 1;
}
}
}
return -1;
}
}
栈
155_最小栈
class MinStack {
Deque<Integer> xStack;
Deque<Integer> minStack;
public MinStack() {
xStack = new LinkedList<>();
minStack = new LinkedList<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
xStack.push(val);
minStack.push(Math.min(minStack.peek(), val));
}
public void pop() {
xStack.pop();
minStack.pop();
}
public int top() {
return xStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/**加粗样式**
394_字符串解码
class Solution {
public String decodeString(String s) {
//栈
//两个栈加一个stirngbuilder
//待倍增的串放在当前串中
//倍增倍数放在一个栈中
//括号之前的串放入栈中,待取
Deque<Integer> stack_num = new LinkedList<>();//存储需要重复字符串的数字
Deque<String> stack_res = new LinkedList<>();//存储待重复的字符串
StringBuilder res = new StringBuilder();
int num = 0;
for(Character x : s.toCharArray()){
if(x == '['){
stack_num.push(num);//把括号前的数字压栈
stack_res.push(res.toString());//把括号前的字母,将来等待被重复的字符串压栈
//需要重复数字归零(注意!因为num可能会是n位数,为了方便后续统一x10处理,必须归零)
num = 0;
res = new StringBuilder();//res重置
}
else if(x == ']'){
int cur_num = stack_num.pop();//此次要重复的次数数字
String last_res = stack_res.pop();//紧贴着这一组括号前面的串
StringBuilder tmp = new StringBuilder();//本次要重复的完整字串
for(int i = 0; i < cur_num; i++){
tmp.append(res);
}
res = new StringBuilder(last_res + tmp);
}
else if (x >= '0' && x <= '9') {
num = num * 10 + (x - '0');//把字符数字转换为数字
}
else{
res.append(x);
}
}
return res.toString();
}
}
动态规划
198_打家劫舍
class Solution {
public int rob(int[] nums) {
//动态规划
//也就是倒推,F(n) = F(n-1) + F(n-2)连锁公式
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}
53_最大子数组和
class Solution {
public int maxSubArray(int[] nums) {
//动态规划
// f[i] = max(f[i-1] + nums[i] , nums[i])
// f[1] = max(f[0] + nums[1] , nums[1])
int max = nums[0];
int maxCurrentTail = 0;
for (int num : nums) {
maxCurrentTail = Math.max(maxCurrentTail + num, num);
max = Math.max(max, maxCurrentTail);
}
return max;
}
}
279_完全平方数
class Solution {
public int numSquares(int n) {
//最多的情况1+1+1....+1 = n
//f[i] = min(f[i-j*j] + 1
//这个1也就代表着j*j的那个完全平方数
//f[0] = 0
//f[1] = min(f[1-j*j]) + 1
int[] f = new int[n+1];
for (int i = 1; i <= n; i++) {
// 不从0开始遍历是因为没有意义,而且初始化数组元素都为0,比较就没意义了
int min = Integer.MAX_VALUE;
for (int j = 1; j * j <= i; j++) {
min = Math.min(min, f[i - j * j]);
}
f[i] = min + 1;
}
return f[n];
}
}
1997_访问完所有房间的第一天
这种方法空间复杂度很高,我自己想的,但是很易理解
class Solution {
public int firstDayBeenInAllRooms(int[] nextVisit) {
// idx = nextVisit[i] ,当count为奇数
// idx = i + 1 ,当count为偶数
// 0 <= nextVisit[i] <= i
// 当第一次访问到最后一个房间时,就是访问完所有房间之时
Map<Integer,Integer> hashMap = new HashMap<>();//记录房间号,访问次数
int date = 0;
int idx = 0;
while (true) {
if (idx == nextVisit.length - 1) {
break;
}
hashMap.put(idx, hashMap.getOrDefault(idx, 0) + 1);
if (hashMap.get(idx) % 2 != 0) {
idx = nextVisit[idx];
}
else{
idx = idx + 1;
}
date++;
}
return date;
}
}
class Solution {
public int firstDayBeenInAllRooms(int[] nextVisit) {
// 设dp[i]为从第0个房间到第i+1个房间的天数
//那么dp[i] - dp[i-1] = 从第奇数次到i房间到第奇数次到i+1房间的天数
//设 f[i]为从第奇数次到i房间到第奇数次到i+1房间的天数
//那么dp[i] - dp[i-1] = f[i]
//f[i] = f[to] + .....+ f[i-1] + 2
//那么又有 dp[i] = f[0] + ...+f[i-1]+f[i]
//dp[i] = 2 * dp[i - 1] - dp[to] + 2
// to为nextVisit[i]
int mod = 1000000007;
int len = nextVisit.length;
int[] dp = new int[len];
dp[0] = 2; //初始化原地待一天 + 访问下一个房间一天
for (int i = 1; i < len; i++) {
int to = nextVisit[i];
dp[i] = 2 + dp[i - 1];
if (to != 0) {
dp[i] = (dp[i] - dp[to - 1] + mod) % mod; //避免负数
}
dp[i] = (dp[i] + dp[i - 1]) % mod;
}
return dp[len - 2]; //题目保证n >= 2
}
}
322_零钱兑换
class Solution {
public int coinChange(int[] coins, int amount) {
//设f[i]为凑成i所需的最少硬币数
//f[i] = min(f[i-j]) + 1
// 边界条件 f[0] = 0
//f[1] = min(f[1-j]) + 1
int[] count = new int[amount+1];
count[0] = 0;
for (int i = 1; i <= amount; i++) {
count[i] = Integer.MAX_VALUE;
for (int coin : coins) {
if(i-coin >= 0 && count[i-coin]!= -1){
count[i] = Math.min(count[i],count[i-coin] + 1);
}
}
if (count[i] == Integer.MAX_VALUE) {
count[i] = -1;
}
}
return count[amount];
}
}
300. 最长递增子序列
class Solution {
public int lengthOfLIS(int[] nums) {
//动态规划
//dp[i] = max(dp[i] , dp[j] + 1) for i in [0,i)\
//注意dp[i]中记录的是以idx为i为此处最长严格递增序列的尾巴的最长严格递增序列长度
//所以最后dp[nums.length - 1]并不一定是此数组的最长严格递增序列长度
//除了这个方法,还有时间复杂度为nlogn的方法
int[] dp = new int[nums.length];
for (int i = 0; i < dp.length; i++) {
dp[i] = 1;
}
int res = 0;
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
//前面的dp[j]肯定是这个位置的最长严格递增序列长度
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
//那么dp[i]的最长严格递增序列长度就是满足dp[i] > dp[j]条件中的
//dp[j] + 1的最大的那个
}
res = Math.max(res, dp[i]);
}
return res;
}
}
岛屿\图论问题
200_岛屿数量
class Solution {
void dfs(char[][] grid, int r, int c) {
//深度优先搜索
int nr = grid.length;
int nc = grid[0].length;
//遍历过一块地,就把它融化掉,防止重复遍历
//当需要区别海地\土地\被遍历过的土地时,可将被遍历过的土地置为2
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
}
994_腐烂的橘子
class Solution {
public int orangesRotting(int[][] grid) {
//广度优先搜索
int M = grid.length;
int N = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
int count = 0;//count 表示新鲜橘子的数量
for(int r= 0; r < M; r++){
for (int c = 0; c < N; c++) {
if (grid[r][c] == 1) {
count++;
}else if (grid[r][c] == 2) {
queue.offer(new int[]{r,c});
}
}
}
int round = 0;//round表示腐烂的轮数,或者分钟数
//这轮腐烂的橘子能感染的感染完了就出栈
//下一轮的橘子接着感染
while(count > 0 && !queue.isEmpty()){
round++;
int n = queue.size();
for (int i = 0; i < n; i++) {
int[] orange = queue.poll();
int r = orange[0];
int c = orange[1];
if (r - 1 >= 0 && grid[r-1][c] == 1) {
grid[r-1][c] = 2;
count--;
queue.add(new int[]{r-1, c});
}
if (r+1 < M && grid[r+1][c] == 1) {
grid[r+1][c] = 2;
count--;
queue.add(new int[]{r+1, c});
}
if (c-1 >= 0 && grid[r][c-1] == 1) {
grid[r][c-1] = 2;
count--;
queue.add(new int[]{r, c-1});
}
if (c+1 < N && grid[r][c+1] == 1) {
grid[r][c+1] = 2;
count--;
queue.add(new int[]{r, c+1});
}
}
}
if (count > 0) {
return -1;
}else{
return round;
}
}
}
前缀和
560_和为 K 的子数组
class Solution {
public int subarraySum(int[] nums, int k) {
//如果说nums中都是非负整数,可以用双指针
//一个用于扩张窗口
//一个用于收缩窗口
//但是现在nums中可以是负数,且k也可以是负数
//可以用前缀和+哈希
//前缀和设计的基础套路就是动态规划
int count = 0, pre = 0;
HashMap < Integer, Integer > mp = new HashMap < > ();
mp.put(0, 1);
for (int i = 0; i < nums.length; i++) {
pre += nums[i];
if (mp.containsKey(pre - k)) {
count += mp.get(pre - k);
}
mp.put(pre, mp.getOrDefault(pre, 0) + 1);
}
return count;
}
}