文章目录
- 算法
- 前缀和
- 双指针法
- 四数之和
- 分治算法
- 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
- 有效数独
- 其他技巧
- 把一个整数变成二进制数据
- 数据类型导致的问题
算法
前缀和
双指针法
这道题目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
数据结构
数组 & 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 有穷自动机
//实现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年提出的一种表达是的表示方法。把运算量写在前面,把运算符写在后面。
- 表达式计算
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是一个双端队列
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
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;
数据类型导致的问题