文章目录

  • 算法
  • 前缀和
  • 双指针法
  • 四数之和
  • 分治算法
  • Offer 58 II 左旋转字符串
  • 回溯算法
  • 动态规划
  • 数据结构
  • 数组 & String & 双指针
  • 字符串
  • 反转字符串
  • T541 反转字符串II
  • 剑指 Offer 05. 替换空格
  • 字符串匹配——KMP的思路
  • 排序
  • 堆排序
  • DFS
  • 缓存方面
  • LRU
  • DFA 有穷自动机
  • 逆波兰表达式[未完成]
  • Java集合
  • JDK中Integer中的parseInt
  • Array 转成 List 或者 Set
  • Arrays.stream
  • DeQueue
  • Deque当作Stack
  • 用队列实现栈
  • Queue
  • 232. 用栈实现队列
  • 单调队列
  • PriorityQueue优先队列
  • Set
  • Map
  • 有效数独
  • 其他技巧
  • 把一个整数变成二进制数据
  • 数据类型导致的问题


算法

前缀和

Fig1

双指针法

这道题目15. 三数之和先对排序,然后在循环里面用双指针

四数之和

public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {
            // 要加上这里的剪支操作
            if (nums[i] > target && (nums[i] >= 0 || target >= 0)) {
                break; // 这里使用break,统一通过最后的return返回
            }
            if (i > 0 && nums[i - 1] == nums[i]) {
                continue;
            }

            for (int j = i + 1; j < nums.length; j++) {
                // 2级剪枝处理
                if (nums[i] + nums[j] > target && (nums[i] + nums[j] >= 0 || target >= 0)) {
                    break;
                }

                if (j > i + 1 && nums[j - 1] == nums[j]) {
                    continue;
                }

                int left = j + 1;
                int right = nums.length - 1;
                while (right > left) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }

分治算法

Offer 58 II 左旋转字符串

思路,分治的思路,把整体先选择一下,然后分部分选择;

public String reverseLeftWords(String s, int n) {
        int len = s.length();
        StringBuilder str = new StringBuilder(s);
        if(n > len){
            n %= len;
        }
        reverse(str, 0, len-1);
        reverse(str, len-1-n+1, len-1);
        reverse(str, 0, len-1-n);
        return str.toString();
    }
    private void reverse(StringBuilder s, int start, int end){
        while(start <= end){
            char t = s.charAt(start);
            s.setCharAt(start, s.charAt(end));
            s.setCharAt(end, t);
            start++;
            end--;
        }
    }

回溯算法

回溯算法

动态规划

最长公共子序列
这种题目不要求连续,只要找出一个字符串 和 另一个字符串 最长的公共字符
https://zhuanlan.zhihu.com/p/311598413

数据结构与算法分析最新版 (877)数据结构与算法分析_字符串

数据结构与算法分析最新版 (877)数据结构与算法分析_List_02

数据结构

数组 & String & 双指针

双指针:删除字符串中的所有相邻重复项
String中常用的技巧双指针
String类API

  • equals和==
  • 常量池
  • internal();
//判断
boolean equals(Object obj)
boolean equalsIgnoreCase(String str)
boolean contains(String str)
boolean startsWith(String str)
boolean endsWith(String str)
boolean isEmpty()

// 获取API
int length()
char charAt(int index)
int indexOf(int ch)
int indexOf(String str)
int indexOf(int ch,int fromIndex)
int indexOf(String str,int fromIndex)
String substring(int start)
String substring(int start,int end)

// 转换
byte[] getBytes()
char[] toCharArray()
static String valueOf(char[] chs)
static String valueOf(int i)
String toLowerCase()
String toUpperCase()
String concat(String str)


// 替换
替换功能
String replace(char old,char new)
String replace(String old,String new)
去除字符串两空格	
String trim()
按字典顺序比较两个字符串  
int compareTo(String str)
int compareToIgnoreCase(String str)

字符串

反转字符串

T541 反转字符串II

剑指 Offer 05. 替换空格

https://leetcode.cn/problems/ti-huan-kong-ge-lcof/ 踩坑点,扩充字符只能append原来的2个,+原来的1个空格就是3个刚好’%20’

字符串匹配——KMP的思路

BF算法需要反复检验重复的字符
for i:=0 to j do
for j:= 0 to j do

KMP的重点:
- 关于失效数组的求法
- 根据失效数组进行回退

next 数组就是一个前缀表,用来回退
,记录了模式串和主串不匹配的时候,模式串应该从何处开始匹配
文本串 aabaabaafa
匹配串 aabaaf
当匹配串的指针指到f,文本串指针指向b,发现不等,此时,文本串指针不动,根据next回退匹配串

下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配就可以了。

// 求next数组

// kmp主体

排序

堆排序

最坏、好、平均时间复杂度O(nlong)
不稳定排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmWO2TJs-1666624361412)(evernotecid://684D322E-432A-47B1-9BC4-9F86BF974051/appyinxiangcom/17588966/ENResource/p1271)]

步骤一、构造初始堆

  • 从最后一个非叶子结点开始

DFS

void dfs()//参数用来表示状态  
{  
    if(到达终点状态)  
    {  
        ...//根据题意添加  
        return;  
    }  
    if(越界或者是不合法状态)  
        return;  
    if(特殊状态)//剪枝
        return ;
    for(扩展方式)  
    {  
        if(扩展方式所达到状态合法)  
        {  
            修改操作;//根据题意来添加  
            标记;  
            dfs();  
            (还原标记);  
            //是否还原标记根据题意  
            //如果加上(还原标记)就是 回溯法  
        }  
 
    }  
}

缓存方面

LRU

https://leetcode.cn/problems/lru-cache/

class LRUCache {

    private static class MyLinkedNode{
        public int key;
        public int val;
        public MyLinkedNode prev;
        public MyLinkedNode next;
        public MyLinkedNode(){}
        public MyLinkedNode(int key, int val){
            this.key = key;
            this.val = val;
        }
    }

    private MyLinkedNode dummyHead = new MyLinkedNode();
    private MyLinkedNode dummyTail = new MyLinkedNode();
    private Map<Integer, MyLinkedNode> map = new HashMap<>();

    private int capacity = 0;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
    }
    
    public int get(int key) {
        if(!map.containsKey(key)){
            return -1;
        }
        MyLinkedNode node = map.get(key);
        moveToFirst(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        MyLinkedNode node;
        if(map.containsKey(key)){
            node = map.get(key);
            node.val = value;
            moveToFirst(node);
        }else{
            node = new MyLinkedNode(key,value);
            map.put(key, node);
            insertFirst(node);
            if(map.size() > capacity){
                // 移除最后一个
                node = removeTail();
                map.remove(node.key);
            }
        } 
        
    }

    private void deleteNode(MyLinkedNode node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
        node.prev = null;
        node.next = null;
    }


    private void insertFirst(MyLinkedNode node){
        node.prev = dummyHead;
        node.next = dummyHead.next;
        dummyHead.next.prev = node;
        dummyHead.next = node;
    }

    private void moveToFirst(MyLinkedNode node){
        deleteNode(node);
        insertFirst(node);
    }

    private MyLinkedNode removeTail(){
        MyLinkedNode node = dummyTail.prev;
        deleteNode(node);
        return node;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

DFA 有穷自动机

数据结构与算法分析最新版 (877)数据结构与算法分析_字符串_03

//实现DFA
class Automaton{


    public int sign = 1;
    public long ans = 0;
    private String state = "start"; //起始态
    Map<String, String[]> map = new HashMap<>(){{  //构建一个状态转移表
        put("start", new String[]{"start", "signed", "in_number", "end"});
        put("signed", new String[]{"end", "end", "in_number", "end"});
        put("in_number", new String[]{"end", "end", "in_number", "end"});
        put("end", new String[]{"end", "end", "end", "end"});
    }};


    public void get(char c){
        state = map.get(state)[get_ele(c)];
        if("in_number".equals(state)){
            ans = ans * 10 + c - '0';
            ans = sign == 1? Math.min(ans,(long) Integer.MAX_VALUE):Math.max(ans, -(long)Integer.MIN_VALUE);
        }else if("sign".equals(state)){
            sign = sign == '+'?1:-1;
        }
    }


    private int get_ele(char c){
        if(c == ' '){
            return 0;
        }
        if(c == '+' || c == '-'){
            return 1;
        }
        if(Character.isDigit(c)){
            return 2;
        }
        return 3;
    }
}

逆波兰表达式[未完成]

后缀表达式,波兰罗辑学家1929年提出的一种表达是的表示方法。把运算量写在前面,把运算符写在后面。

数据结构与算法分析最新版 (877)数据结构与算法分析_1024程序员节_04

  1. 表达式计算
    4https://www.acwing.com/problem/content/153/

Java集合

JDK中Integer中的parseInt

int limit = -Integer.MAX_VALUE;
int multmin = limit / radix;
int result = 0;
while (i < len) {
    // Accumulating negatively avoids surprises near MAX_VALUE
    int digit = Character.digit(s.charAt(i++), radix);
    if (digit < 0 || result < multmin) {
        throw NumberFormatException.forInputString(s);
    }
    result *= radix;
    if (result < limit + digit) {
        throw NumberFormatException.forInputString(s);
    }
    result -= digit;
}
return negative ? result : -result;

Array 转成 List 或者 Set

List list = Arrays.asList(数组);
Set<数据类型> set = new HashSet<>(Arrays.asList(数组));
// List 转成 Set
Set<数据类型> set = new HashSet<>(list);
数据类型[] arr = list.toArray(new 数据类型(list.size()));
// Set 转 list
List<数据类型> list = new ArrayList<>(set);


Object[] toArray();
<T> T[] toArray(T[] a);


List<String> strList = new ArrayList<>();
strList.add("list-a");
strList.add("list-b");
String[] strArray = strList.toArray(new String[strList.size()]);


Arrays.asList(“a”,”b”,”c”); //Arrays.asList(T… a)

Arrays.stream(digits);    //会把数组编程steam

Stream.of(digits);        //return stream




List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");//size=7


List<String> filtered = strings
        .stream()
        .filter(string -> !string.isEmpty())
        .collect(Collectors.toList());//size=5 把""过滤掉了
System.out.println("筛选列表: " + filtered);//筛选列表: [abc, bc, efg, abcd, jkl]


String mergedString = strings
        .stream()
        .filter(string -> !string.isEmpty())
        .collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);//合并字符串: abc, bc, efg, abcd, jkl

Arrays.stream

Stream<String> stream = Arrays.stream(arr);            
// Displaying elements in Stream         
stream.forEach(str -> System.out.print(str + " "));

DeQueue

Deque的API:Deque是一个双端队列

数据结构与算法分析最新版 (877)数据结构与算法分析_1024程序员节_05

Deque的使用场景,不涉及到并发操作,有两个实现类、

  • LinkedList 大小可变的链表双端队列,允许元素为插入null。
  • ArrayDeque 大下可变的数组双端队列,不允许插入null。
  • ConcurrentLinkedDeque 大小可变且线程安全的链表双端队列,非阻塞,不允许插入null。
  • LinkedBlockingDeque 为线程安全的双端队列,在队列为空的情况下,获取操作将会阻塞,直到有元素添加。

Deque当作Stack

2. 
 private Deque<Integer> data = new ArrayDeque<Integer>();   
  public void push(Integer element) {   
    data.addFirst(element);   
  }   
  public Integer pop() {   
    return data.removeFirst();   
  }   
  public Integer peek() {   
    return data.peekFirst();   
  }   

可以直接用
Deque<Character> stack = new LinkedList<>();
stack.peek();
stack.push();
stack.pop();

用队列实现栈

队列实现栈的思路

q1 作为主要的栈,q2 作为辅助栈,每次push 操作先放到q2中,在把q1的数据出队放到q2,然后交换q1、q2
class MyStack {
    Deque<Integer> q1 = new LinkedList<>();
    Deque<Integer> q2 = new LinkedList<>();
    public MyStack() {

    }
    
    public void push(int x) {
        q2.offer(x);
        while(!q1.isEmpty()){
            q2.offer(q1.poll());
        }
        Deque<Integer> t = q1;
        q1 = q2;
        q2 = t;
    }
    
    public int pop() {
        return q1.poll();
    }
    
    public int top() {
        return q1.peek();
    }
    
    public boolean empty() {
        return q1.isEmpty();
    }
}

Queue

数据结构与算法分析最新版 (877)数据结构与算法分析_数据结构与算法分析最新版_06

232. 用栈实现队列

class MyQueue {
    Deque<Integer> s1 = new LinkedList<>();
    Deque<Integer> s2 = new LinkedList<>();
    public MyQueue() {
    }
    public void push(int x) {
        s1.push(x);
    }
    public int pop() {
        dumpstackIn();
        return s2.pop();
    }
    public int peek() {   
        dumpstackIn();
        return s2.peek();
    }
    private void dumpstackIn(){
        if(s2.isEmpty())
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
        return ;
    }
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}

单调队列

https://leetcode.cn/problems/sliding-window-maximum/

这个题目只需要维护一个单调减队列,让队头始终保持最大元素。

PriorityQueue优先队列

二叉堆:有序的完全二叉排序树,
中间节点k/2,左儿子2k,右儿子2k+1
堆的插入

  • 放在堆尾,然后调整,上浮到何时位置

堆的删除

  • 我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置

Set

// 常用方法
add(Object obj);
Interator iterator = set.iterator();
while(iterator.hasNext()){

    iterator.next();
}
// 边迭代边修改的问题
// solution,使用iterator


// foreach中,会使用.next() 在删除元素之后被调用 ConcurrentModificationException

HashSet与LinedHashSet

  • HashSet
  • LinkedHashSet,有序,插入性能没有HashSet好,但是迭代访问要好
  • TreeSet:不能写入null、写入的数据是有序的。所以有序 + 去重复就用这

TreeSet底层是红黑树

Map

遍历Map的用法

for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                majorityEntry = entry;
            }
}

有效数独

判断行、列没有重复的1-9,以及一个小的3 * 3数字没有重复的1-9
这道题目,人家用hash做的解法比自己简单不少,在求小方格的时候,并没有直接去做,而是用计算关系去弄,很精彩。

class Solution {
    public boolean isValidSudoku(char[][] board) {
        Map<Integer, Set<Integer>> row  = new HashMap<>(), col = new HashMap<>(), area = new HashMap<>();
        for (int i = 0; i < 9; i++) {
            row.put(i, new HashSet<>());
            col.put(i, new HashSet<>());
            area.put(i, new HashSet<>());
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                char c = board[i][j];
                if (c == '.') continue;
                int u = c - '0';
                int idx = i / 3 * 3 + j / 3;
                if (row.get(i).contains(u) || col.get(j).contains(u) || area.get(idx).contains(u)) return false;
                row.get(i).add(u);
                col.get(j).add(u);
                area.get(idx).add(u);
            }
        }
        return true;
    }
}

作者:AC_OIer
链接:https://leetcode.cn/problems/valid-sudoku/solution/gong-shui-san-xie-yi-ti-san-jie-ha-xi-bi-ssxp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

其他技巧

把一个整数变成二进制数据

// 计算给定的整数的二进制表示中的 1 的数目
Integer.bitCount
public static int bitCount(int i) {
    // HD, Figure 5-2
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

// 2 Brain Kernighan算法
// 对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0
int cnt = 0;
while(x != 0){
    x &= (x-1);
    cnt++;
}

// 3 动态规划 最高有效位
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++){
    // 寻找 j<=x && j/2==0
    if(i & (i-1) == 0){
        y = i;
        dp[i] = dp[0] + 1;
    }else{
        y = i-1;
        dp[i] = dp[y] + 1;
    }
}

// 4 动态规划 最低有效位
bit[x] = bit[x>>1] + (x&1);

// 5 动态规划—最低设置位
bits[x] = bits[x & (x-1)] + 1;

数据类型导致的问题