基础算法与数据结构

一、滑动窗口,特殊的双指针(适合求不确定子串长度)

思想:所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

题目:给定一个含有 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数组去记录是否使用过,可以用来去重。

贪心算法

全靠经验,规律性比较差,基本思想是,可以通过局部最优解推导出全局最优解。贪心的本质是选择每一阶段的局部最优,从而达到全局最优

动态规划

如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。动态规划的五步法如下:

  1. 确定dp数组(dp table)以及下标的含义

  2. 确定递推公式

  3. dp数组如何初始化

  4. 确定遍历顺序

  5. 举例推导dp数组

    背包问题代码随想录 (programmercarl.com)

编辑距离问题,值得深思考虑: 代码随想录 (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;
    }
}

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值