1. 冒泡排序(O(n2))
把最大的沉到最后,两两交换
public void bubbleSort(int[] nums) {
// 外层循环控制排序的轮数
for (int i = nums.length - 1 ; i > 0; i--) {
boolean isSorted = true;
// 内层循环每次将未排序序列中最大的数冒泡到最后端
for (int j = 0; j < i;j++) {
// 升序
if (nums[j] > nums[j+1]) {
swap(nums,j,j+1);
isSorted = false;
}
}
if (isSorted) {
break;
}
}
}
2. 快速排序(O(nlogn))
哨兵
public void quickSort(int[] nums,int l, int r) {
if (l < r) {
int q = partition(nums,l,r);
quickSort(nums,l,q-1);
quickSort(nums,q+1,r);
}
}
private int partition(int[] nums, int l, int r) {
// 临界值,哨兵
int tmp = nums[l];
int i = l, j = r;
while (i != j) {
// 都带等号
while (i < j && tmp <= nums[j]) {
j--;
}
while (i < j && tmp >= nums[i]) {
i++;
}
if (i < j) {
swap(nums,i,j);
}
}
// 将基准数归位,放在中间位置
nums[l] = nums[i];
nums[i] = tmp;
return i;
}
3. 选择排序(O(n2))
每次选出最小的放到端点处
public void selectSort(int[] nums) {
for (int i = 0 ; i < nums.length; i++) {
int index = i;
for (int j = i+1; j < nums.length;j++) {
// 找出最小的,放在端点处
if (nums[j] < nums[index]) {
index = j;
}
}
swap(nums,i,index);
}
}
4. 插入排序(O(n2))
把当前的数(nums[i])插入到之前已经排好序的数组中
public void insertSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int tmp = nums[i];
int j = i;
while (j > 0 && nums[j-1] > tmp) {
nums[j] = nums[j-1];
j--;
}
nums[j] = tmp;
}
}
5. 二分插入排序(O(nlogn))
public void binaryInsertSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int tmp = nums[i];
int l = 0, r = i-1;
// l <= r
while (l <= r) {
int mid = l + (r-l)/2;
if (nums[mid] < tmp) {
l = mid + 1;
}
else {
r = mid - 1;
}
}
// 要插入的位置之后的都后移
for (int j = i - 1;j >= l; j--) {
nums[j+1] = nums[j];
}
// 插入
nums[l] = tmp;
}
}
1. 判断相交
相同的尾节点
2. 找到交点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode l1 = pHead1, l2 = pHead2;
while (l1 != l2) {
l1 = (l1 == null) ? pHead2 : l1.next;
l2 = (l2 == null) ? pHead1 : l1.next;
}
return l1;
}
3. 判断环
快慢指针
4. 找到环的入口
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if (pHead == null || pHead.next == null || pHead.next.next ==null) {
return null;
}
ListNode fast = pHead.next.next;
ListNode slow = pHead.next;
// 判断有环
while (slow != fast) {
if (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
else {
return null;
}
}
fast = pHead;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
5. 链表反转
public ListNode ReverseList(ListNode head) {
ListNode pre = null, next = null, reverseHead = null;
ListNode pNode = head;
while (pNode != null) {
next = pNode.next;
pNode.next = pre;
pre = pNode;
pNode = next;
if (pNode == null) {
reverseHead = pre;
}
}
return reverseHead;
}
6. 删除链表中的重复节点
新建一个头结点,防止第一个数就重复;用pre指针保存前一个节点;直接比较pnode.next.val == pnode.val,直到找到最后一个相等的值
public ListNode deleteDuplication(ListNode pHead)
{
if (pHead == null || pHead.next == null) {
return pHead;
}
ListNode newHead = new ListNode(-1);
newHead.next = pHead;
ListNode pre = newHead;
ListNode pNode = newHead.next;
while (pNode != null) {
if (pNode.next != null && pNode.val == pNode.next.val) {
// 找到最后一个相同的节点
while (pNode.next != null && pNode.val == pNode.next.val) {
pNode = pNode.next;
}
pre.next = pNode.next;
pNode = pNode.next;
}
else {
pre = pre.next;
pNode = pNode.next;
}
}
return newHead.next;
}
7. 倒数第k个节点
一个先走k-1步,另一个再开始,第一个到头的时候第二个所在的位置
public ListNode FindKthToTail(ListNode head,int k) {
if (head == null || k < 0) {
return null;
}
ListNode p1 = head,p2= head;
for (int i = 0 ; i < k; i++) {
if (p1 == null ){
return null;
}
p1 = p1.next;
}
while (p1 != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
8. 合并两个有序链表
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
if (list1.val <= list2.val) {
list1.next = Merge(list1.next,list2);
return list1;
}
else {
list2.next = Merge(list1,list2.next);
return list2;
}
}
9. 复制链表
https://blog.csdn.net/weixin_39795049/article/details/89036538
1. 判断root2是不是root1的子结构
两个递归函数,作用分别是:1找到r1中与r2根节点相同的节点;2再判断r1中以该根为节点的树有没有和r2相同的结构。
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if (root2 == null) return false;
boolean result = false;
if (root1 != null && root2 != null) {
if (root1.val == root2.val) {
result = doesRoot1HasRoot2(root1,root2);
}
if (!result) {
result = HasSubtree(root1.left,root2);
}
if (!result) {
result = HasSubtree(root1.right,root2);
}
}
return result;
}
private boolean doesRoot1HasRoot2(TreeNode root1, TreeNode root2) {
if (root2 == null) {
return true;
}
if (root1 == null) {
return false;
}
if (root1.val != root2.val) {
return false;
}
return doesRoot1HasRoot2(root1.left,root2.left) && doesRoot1HasRoot2(root1.right,root2.right);
}
2. 二叉树的镜像
先交换左右节点,再调整树里面的结构
public void Mirror(TreeNode root) {
if (root == null)
return;
if (root.left == null && root.right == null) {
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left != null) {
Mirror(root.left);
}
if (root.right != null) {
Mirror(root.right);
}
}
3. 二叉树层序遍历(队列)
public ArrayList PrintFromTopToBottom(TreeNode root) {
ArrayList list = new ArrayList<>();
if (root == null) {
return list;
}
Queue queue = new LinkedList<>();
queue.add(root);
list.add(root.val);
boolean flag = false;
while (!queue.isEmpty()) {
TreeNode pnode = queue.poll();
if (flag) {
list.add(pnode.val);
}
if (pnode.left != null) {
queue.add(pnode.left);
//list.add(pnode.left.val);
}
if (pnode.right != null) {
queue.add(pnode.right);
//list.add(pnode.right.val);
}
if (!flag) {
flag = true;
}
}
return list;
}
3. BST的后序遍历序列
递归,BST后序,最后一个肯定是根节点。然后遍历找到第一个比根节点大的,就是右子树开始。然后递归,子树也具有相同的性质。
private boolean judge(int[] sequence, int l,int r) {
int root = sequence[r];
int i = l;
for (i = l ;i < r; i++) {
if (sequence[i] > root) {
break;
}
}
//此时的i是右子树后序遍历的第一个节点(右子树左下角)
int j = i;
for (j = i; j < r ;j++) {
if (sequence[j] < root) {
return false;
}
}
boolean left = true;
// 左子树存在
if (i > l) {
left = judge(sequence,l,i-1);
}
boolean right = true;
// 右子树存在
if (i < r) {
right = judge(sequence,i,r-1);
}
return left && right;
}
4. 二叉树中和为某一值的序列
注意:序列是从根到叶子节点;按长度排序,使用lamda表达式
ArrayList path = new ArrayList<>();
ArrayList> list = new ArrayList<>();
public ArrayList> FindPath(TreeNode root,int target) {
help(root, target);
Collections.sort(list, (o1,o2)->o2.size()-o1.size());
return list;
}
public ArrayList> help(TreeNode root, int target) {
if (root == null) {
return list;
}
target = target - root.val; //因为任何一个路径都一定包括根节点
if (target >= 0) {
path.add(root.val);
if (target == 0 && root.left == null && root.right == null) {
list.add(new ArrayList(path)); //不能是list.add(path);不重新new的话从始至终listAll中所有引用都指向了同一个一个list
}
FindPath(root.left,target); //深度遍历,必须先左后右
FindPath(root.right,target);
path.remove(path.size()-1); //移除最后一个元素,深度遍历完一条路径之后要回退。因为如果遍历到叶子节点发现此叶子节点不符合,需要将次叶节点从list里删除,回溯到叶节点的父节点。
//并不是没找到路径要回退,找到了也要回退的。因为一直使用了同一个list的空间,遍历完一条路径后回退去找别的路径一定要从list后面删掉回退的元素,不然list会越来越长。
}
return list; //return list; 这一句在函数里是没有实际意义的,因为返回之后,没有任何操作,而且listAll是全局变量,所以在函数运行的过程中,可以被加入符合要求的路径。
}
}
5. 二叉树与双向链表
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) return null;
if (pRootOfTree.left == null && pRootOfTree.right == null) return pRootOfTree;
//1. 左子树构成双链表,返回头结点
TreeNode left = Convert(pRootOfTree.left);
TreeNode tmp = left;
//2. 定位至左子树最后一个节点
while (tmp != null && tmp.right != null) {
tmp = tmp.right;
}
//3. 如果左子树不为空,则将root追加
if (left != null) {
tmp.right = pRootOfTree;
pRootOfTree.left = tmp;
}
//4. 将右子树构成双链表
TreeNode right = Convert(pRootOfTree.right);
//5. 将右子树链表追加到root之后
if (right != null) {
pRootOfTree.right = right;
right.left = pRootOfTree;
}
return left==null ? pRootOfTree : left;
}
6. 判断一棵二叉树是不是对称的
boolean isSymmetrical(TreeNode pRoot)
{
if (pRoot == null) {
return true;
}
return symmetricalHelper(pRoot.left,pRoot.right);
}
private boolean symmetricalHelper(TreeNode left, TreeNode right) {
if (left == null) return right == null;
if (right == null) return false;
if (left.val != right.val) return false;
return symmetricalHelper(left.left,right.right) && symmetricalHelper(left.right,right.left);
}
7. 之字形打印二叉树
层序遍历的变种,要每一行遍历顺序反过来(用两个栈,因为每次压进去的时候出栈就会反过来,奇数层的时候其子节点从左至右压入s2,偶数层的时候其子节点从右至左压入s1)
public ArrayList> Print(TreeNode pRoot) {
ArrayList row = new ArrayList<>();
ArrayList> res = new ArrayList<>();
if (pRoot == null) {
return res;
}
Stack s1 = new Stack<>();
Stack s2 = new Stack<>();
s1.push(pRoot);
int layer = 1;
while (!s1.isEmpty() || !s2.isEmpty()) {
if (layer % 2 != 0) {
while (!s1.isEmpty()) {
TreeNode tmp = s1.pop();
row.add(tmp.val);
if (tmp.left != null) {
s2.push(tmp.left);
}
if (tmp.right != null) {
s2.push(tmp.right);
}
}
}
else {
while (!s2.isEmpty()) {
TreeNode tmp = s2.pop();
row.add(tmp.val);
if (tmp.right != null) {
s1.push(tmp.right);
}
if (tmp.left != null) {
s1.push(tmp.left);
}
}
}
if (!row.isEmpty()) {
res.add(row);
row = new ArrayList<>();
layer++;
}
}
return res;
}
8. 二叉树的下一个中序遍历节点
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if (pNode == null) {
return pNode;
}
// 如果该节点有右孩子,则一定指向其右孩子的最下面的左孩子
if (pNode.right != null) {
TreeLinkNode right = pNode.right;
while (right.left != null) {
right = right.left;
}
return right;
}
// 节点不是根节点,如果是父节点的左孩子,则直接返回父节点
while (pNode.next != null) {
TreeLinkNode parent = pNode.next;
if (pNode == parent.left) {
return parent;
}
//----------------------------重点----------------------
// 如果是父节点的右孩子,则返回其父节点的父节点,重复判断是不该父节点的左孩子
pNode = pNode.next;
}
return null;
}
9. 层序打印二叉树
ArrayList> Print(TreeNode pRoot) {
ArrayList row = new ArrayList<>();
ArrayList> res = new ArrayList<>();
if (pRoot == null) {
return res;
}
int start = 0, end = 1;
Queue queue = new LinkedList<>();
queue.add(pRoot);
while (!queue.isEmpty()) {
TreeNode tmp = queue.poll();
row.add(tmp.val);
start++;
if (tmp.left != null) {
queue.add(tmp.left);
}
if (tmp.right != null) {
queue.add(tmp.right);
}
if (start == end) {
end = queue.size();
start = 0;
//=================重点=============
res.add(row);
row = new ArrayList<>();
}
}
return res;
}
10. 序列化和反序列化二叉树
本来,是前序+中序(或后+中)可以还原一棵二叉树。但是现在,我们对null进行特殊字符处理,直接用前序进行序列化。
String Serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
if (root == null) {
sb.append("#,");
return sb.toString();
}
sb.append(root.val+",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
int start = -1;
TreeNode Deserialize(String str) {
start++;
if (start >= str.length()) {
return null;
}
String[] nodes = str.split(",");
if (start < nodes.length && !nodes[start].equals("#")) {
TreeNode pnode = new TreeNode(Integer.valueOf(nodes[start]));
pnode.left = Deserialize(str);
pnode.right = Deserialize(str);
return pnode;
}
return null;
}
11. 二叉搜索树的第k大节点
TreeNode KthNode(TreeNode pRoot, int k)
{
if (pRoot == null) {
return null;
}
Stack stack = new Stack<>();
TreeNode pnode = pRoot;
int count = 0;
while (pnode != null || !stack.isEmpty()) {
if (pnode != null) {
stack.push(pnode);
pnode = pnode.left;
}
// stack不空
else {
TreeNode tmp = stack.pop();
count++;
if (count == k) {
return tmp;
}
pnode = tmp.right;
}
}
return null;
}
三个线程顺序打印自己的名字一遍(join)
public class TestPrintOrder1 {
public static void main(String[] args) throws InterruptedException {
Thread testA = new TestThread("A");
Thread testB = new TestThread("B");
Thread testC = new TestThread("C");
testA.start();
testA.join();
testB.start();
testB.join();
testC.start();
}
}
class TestThread extends Thread{
String name = "";
public TestThread(String name){
this.name = name;
}
public void run(){
System.out.print(name);
}
}
三个线程顺序打印自己的名字十遍(notifyAll)
public class TestPrint {
static int count = 0;
static final Object obj = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {
if (count % 3 == 0) {
System.out.println("A");
count++;
obj.notifyAll();
} else
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {
if (count % 3 == 1) {
System.out.println("B");
count++;
obj.notifyAll();
} else
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {
if (count % 3 == 2) {
System.out.println("C");
count++;
obj.notifyAll();
} else
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
public void fun() {
t3.start();
t1.start();
t2.start();
}
public static void main(String[] args) {
TestPrint tp = new TestPrint();
long t1 = System.currentTimeMillis();
tp.fun();
while (true) {
if (System.currentTimeMillis() - t1 >= 10)// 运行10个毫秒
System.exit(0);
}
}
1. 数组中超过一半的数字(hash、阵地攻守)
2. 连续子数组的最大和(动态规划)
3. 最小元素的栈(辅助栈)
4. 判断是否是栈的弹出序列(辅助栈)
for (int i = 0,j = 0; i < pushA.length; i++) {
assistStack.push(pushA[i]);
while (!assistStack.empty() && assistStack.peek() == popA[j]) {
j++;
assistStack.pop();
}
}
if (assistStack.empty()) {
isOrder = true;
}
5. 和为S的两个数字
双指针/hashMap(map.put(arr[i],i),判断map.containsKey(sum-arr[i]))&& i != map.get(sum-arr[i]))
6. 第k大的数/最小的k个数
1) 快排思想,只用partition,不用sort。如果剩下的一半个数==k,就直接输出(O(n))
2)如果不能改变原数组,使用堆,java中priorityQueue是根据小顶堆实现的。改成有k个数的大顶堆,然后每次发现比堆顶小的就移除堆顶,加入进去。(O(nlogk))
7. 第一个只出现一次的字符
两次HashMap,分别保存下标和出现的次数。其中,countMap使用LinkedHashMap,保证存取顺序相同,就可以直接取出是第一次出现的元素
8. 数字在排序数组中出现的次数(HashMap)
如果是有序数组,二分!!
二分,分别找到第一次出现和最后一次出现的位置。左右0.5,找到该插入的地方即可。
9. 数组中除了两个数出现了一次之外剩下的都出现了两次
两次异或,第一次的结果是两个出现一次的数的异或结果;第二次先找到这个值中第一位为1的,表示这两个数中这一位一个为0一个为1,所以可以把整个数组分为两组,再进行异或,每一组中只有一个数出现了一次,就分别找到了这两个数。
10. 和为S的正整数序列
滑动窗口
11. 两个数交换不使用第三个变量
两种方法:
1. a=b-a; b=b-a; a=b+a;
2. a=a^b; b=a^b; a=a^b;
12. 左旋字符串
三次旋转(YX = (XTYT)T)(先X,再Y,然后全部)
同理,右旋字符串,右旋n = 左旋len-n,所以还是可以这样写。
13. 旋转字符串
“student. a am I”。这种,即 单词内部是正确的,但是单词之间倒过来了。
两次旋转,先转全部,再转每个单词内部
14. 每次喊到m-1的退出(约瑟夫环?)
bt = 0;
bt = (bt + m - 1) % list.size();
15. 1+2+3..+n不用while、if等
用&&的短路性质实现递归的退出(短路就是如果前面是false后面就不执行)
boolean ans = (n > 0) && (sum += Sum_Solution(n-1))>0;
16. 不用加减乘除做加法
加法:异或操作^;进位:与操作&再左移一位。
按照十进制的思路来,例如5+7
二进制就是101+111,先算加法(不带进位)即101^111=010;再算进位(纯进位)(101&111)<<1 = 1010;
再重复上面的(此时num1=加法结果010,num2=进位结果1010)直到进位为0
17. 数组中重复的数字(长度为n,大小都在0~n-1中)
1) 布尔数组;
2)归位,将numers[i]放到numbers[numbers[i]]的位置上,(即将k放到第k位),如果第k位本身就是k,则就重复;
3)遇到numbers[i]就将numbers[numbers[i]]上的数+n,因为本来都是0~n-1之间的数字,所以再下次遇到本来就比n大的数字就断定该位重复。
18. 构建乘积数组
B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1],不能使用除法
上下三角分开计算,可以重复利用。(先计算下三角,再上三角)
19. 正则表达式匹配
递归,正向思路(比较容易想,主要是有*的情况);
动态规划,逆向思路
20. 运用正则表达式(比如判断字符串是不是数字)
[]表示匹配任意一个;?表示0/1个,+表示1/多个,*表示0/多个,^表示以其开头,$表示以其结尾。
21. 排序数组去重(O(n))
快慢指针,一个从0,一个从1,
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
22. 洗牌算法
就是随机打乱,每个数都不在之前的位置上
从len-1开始往前,
tempInd=rdm.nextInt(i);
this.swap(curList[i], curList[tempInd]);
23. 滑动窗口最大值
双端队列,用一个双端队列保存当前窗口的最大值的下标,
保证队列首元素为当前窗口最大值下标
for (int i = 0; i < num.length; i++) {
// 来了一个新的值,队列中保存的值一定是没有过期的,所以就要和队尾的元素比较,清除队列中所有比当前元素小的值
// 且队首元素时最大的,所以可以优化一下,如果比队首元素还大,就直接clear
if (!queue.isEmpty() && num[i] > num[queue.peekFirst()]) {
queue.clear();
}
else {
while (!queue.isEmpty() && num[queue.peekLast()] < num[i]) {
queue.removeLast();
}
}
queue.add(i);
// 判断过期
if (queue.peekFirst() <= i - size) {
queue.pollFirst();
}
// 当滑动窗口首地址i大于等于size时才开始加入集合
if (i >= size - 1) {
list.add(num[queue.peekFirst()]);
}
}
24. 最长公共子串
动态规划,然后遍历选出最大的
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = 0;
}
}
25. 矩阵中的路径
回溯,DFS,visited
int[][] direction = {{0,-1},{1,0},{0,1},{-1,0}};
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
if (matrix == null || matrix.length <= 0 || str == null || cols <= 0 || rows <= 0) {
return false;
}
boolean[][] visited = new boolean[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (dfs(0,matrix,rows,cols,i,j,str,visited)) {
return true;
}
}
}
return false;
}
private boolean dfs(int curLen, char[] matrix, int rows, int cols, int i, int j, char[] str, boolean[][] visited) {
if (curLen == str.length) {
return true;
}
if (i < 0 || i >= rows || j < 0 || j >= cols || visited[i][j] || i*cols+j < 0 || matrix[i*cols+j] != str[curLen]) {
return false;
}
visited[i][j] = true;
for (int[] d : direction) {
if (dfs(curLen+1,matrix,rows,cols,i+d[0],j+d[1],str,visited)) {
return true;
}
}
visited[i][j] = false;
return false;
}
26. 机器人运动路径
不带回溯的dfs,和上一题的区别就是不用在最后归位visited
27. 数据流中的中位数
双堆。大顶堆存放较小的数;小顶堆存放较大的数。
要保证两个堆差不多平衡,所以可以先进大顶堆,然后取出堆顶(最大的)进入最小堆。
首先要保证数据平均分配到两个堆中,还要保证所有最大堆中的数据要小于最小堆中的数据。
28. 反转字符串
要不就是sb.reverse()
或者 前后头尾交换,i 29. 查找由连续数字组成的数组中缺失的数 等差数列求和减去现在的和 30. 实现环形队列 判空:return rear == front; 入队(rear): 判满:if((rear +1) % MAXSIZE == front) { return false; } arr[rear] = value; rear = (rear + 1) % MAXSIZE; return true; 出队(front): 判空 Object obj = a[front]; front = (front+1)%a.length; 31. 单链表排序 归并 https://www.bbsmax.com/A/WpdK7ljodV/ 32. 三数之和为0 https://www.cnblogs.com/gzshan/p/11127130.html https://blog.csdn.net/weixin_39784818/article/details/93712318?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2 1)排序+hash 2)排序+双指针(去重)