一、滑动窗口,特殊的双指针(适合求不确定子串长度)
思想:所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
题目:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口,不断调整子序列的起始位置
int l = Integer.MAX_VALUE;
int left = 0, sum = 0;
for(int right = 0; right < nums.length; right++)
{
sum += nums[right];
//滑动窗口的精髓所在,不断改变长度
while(sum >= target)
{
l = l > right - left + 1 ? right - left + 1:l;
sum -= nums[left++];
}
}
return l == Integer.MAX_VALUE ? 0:l;
}
}
二、给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。(模拟的一个过程)

示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
链接:https://leetcode-cn.com/problems/spiral-matrix-ii
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:如上图所示:
-
填充上行从左到右
-
填充右列从上到下
-
填充下行从右到左
-
填充左列从下到上
由外向内一圈一圈这么画下去
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开(如上图所示,最后一个元素留给下一行或者列),或者左开又闭的原则,这样这一圈才能按照统一的规则画下来
class Solution {
public int[][] generateMatrix(int n) {
//模拟题目需要细心,数组的操作,左闭右开的思路
int[][] ans = new int[n][n];
int count = 1;//从1开始赋值
int loop = n/2;//需要循环的次数,n=3需要一次,以此类推
int offtset = 1;//每次需要大的补偿位
//开始循环的的起始位置
int startX = 0;
int startY = 0;
while(loop -- > 0)
{
int i = startX;
int j = startY;
//先是上面的行
for(j = startY; j < startY + n - offtset; j ++)
ans[i][j] = count ++;
//再是右边的列
for(i = startX; i < startX + n - offtset; i ++)
ans[i][j] = count ++;
//再是下面的行
for(;j > startY; j--)
ans[i][j] = count ++;
//再是左边的列
for(;i > startX; i--)
ans[i][j] = count ++;
startX ++;
startY ++;
offtset +=2;//每次循环一次前后左右的长度减少两个
}
if(n % 2 == 1)
{
ans[n/2][n/2] = count;//奇数的时候需要补齐最中间的一个
}
return ans;
}
}
三、链表
例题:lc24题 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
输入:head = [1,2,3,4]
输出:[2,1,4,3]
/**
* 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) {
//题目不是特别难,但是很绕,画图做一下,可以定义一个头节点,这样就方便多了
if(head == null || head.next == null)
return head;
ListNode c = head;
ListNode n = c.next;
ListNode p = new ListNode(-1);//定义一个虚拟头节点
p.next = head;
head = p;
while(n != null)
{
c.next = n.next;
n.next = c;
p.next = n;
p = c;
c = c.next;
if(c != null)
n = c.next;
else
n = null;
}
return head.next;
}
}
四、哈希表
Map.getOrDefault(Object key, V defaultValue)方法的作用是:
当Map集合中有这个key时,就使用这个key值;
如果没有就使用默认值defaultValue
五、字符串
StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全。StringBuilder一般比较高效。String是不可变类。
1、如果有字符串移动翻转的题型,在不使用额外的空间的时候,可以考虑整体翻转和局部翻转的方法。
2、在一个串中查找是否出现过另一个串,KMP算法是最高效的选择。KMP的经典思想就是:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配详解见:代码随想录 (programmercarl.com)
六、回溯算法
回溯的模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
*注意:组合问题与子集问题,组合问题相当于需要树的叶子结点,子集问题相当于需要所有树的所有结点。要清楚子集问题和组合问题、分割问题的的区别,子集是收集树形结构中树的所有节点的结果。而组合问题、分割问题是收集树形结构中叶子节点的结果。
*去重问题,可以使用一个used数组去记录是否使用过,可以用来去重。
贪心算法
全靠经验,规律性比较差,基本思想是,可以通过局部最优解推导出全局最优解。贪心的本质是选择每一阶段的局部最优,从而达到全局最优
动态规划
如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。动态规划的五步法如下:
-
确定dp数组(dp table)以及下标的含义
-
确定递推公式
-
dp数组如何初始化
-
确定遍历顺序
-
举例推导dp数组
编辑距离问题,值得深思考虑: 代码随想录 (programmercarl.com)
注意子序列和子串的区别,子序列可以不连续,子串必须连续。
回文串算法
以寻找最长回文串为例
1、暴力法,三层循环
2、中心扩展法,分为偶数和奇数两种情况,可以写一个通用的函数计算两层循环
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(two_points(s));
}
public static int two_points(String s){
int len = 0;
for(int i = 0; i < s.length() - 1; i++){
len = Math.max(len, Math.max(ishuiwen(s, i, i), ishuiwen(s, i, i + 1)));
}
return len;
}
public static int ishuiwen(String s, int b, int e){
int len = 0;
while(b >= 0 && e < s.length()){
if(s.charAt(b) != s.charAt(e))
break;
b--;
e++;
}
len = e - b - 1;
return len;
}
}
未完待续