一 算法入门
1 常见位运算
- 位与(&):二元运算符,两个为1时结果为1,否则为0
- 位或(|):二元运算符,两个其中有一个为1时结果就为1,否则为0
- 位异或(^):二元运算符,两个数同时为1或0时结果为1,否则为0 (可以理解为无进位相加)
- 位取非(~):一元运算符,取反操作
- 左移(<<):一元运算符,按位左移一定的位置。高位溢出,低位补符号位,符号位不变。
- 右移(>>):一元运算符,按位右移一定的位置。高位补符号位,符号位不变,低位溢出。
- 无符号右移(>>>):一元运算符,符号位(即最高位)保留,其它位置向右移动,高位补零,低位溢出。
认识异或运算:
- 满足结合律 (a ^ b)^ c = a ^ (b ^ c)
- 满足交换律 a ^ b = b ^ a
- 满足自反性 a ^ a = 0 a ^ b ^ b = a ^ 0=a
2 位运算技巧
- 取mid
// 常见写法
int mid = (l + r)/2;
// l+r容易导致溢出
int mid = r + (r - l)/2;
// 位运算优化
int mid = l + ((r - l) >> 1);
- N*2+1
// 常见写法 例如 11 = 5 * 2 + 1
int sum = N * 2 + 1;
// 位运算写法 0101 << 1
// 0 1010舍弃最高位为1010 值为10
// 1010 | 1 为 1011 为11
int sum = (N << 1 | 1);
3 位运算题目
1 如何不用额外变量交换两个数
int a = 3;
int b = 5;
// 通常swap方法 这种方法需要多申请一个变量 增加了常数项的空间复杂度
int temp = a;
a = b;
b = temp;
// 通过位运算 不用开辟额外空间,并且计算速度更快
a = a^b;
b = a^b;
a = a^b;
2 一个数组中有一组数出现了奇数次,其他出现偶数次,找到并打印这种数
// 方法一 最暴力的使用map 比较简单 不进行演示
// 缺点:需要开辟额外空间map
// 方法二 使用位运算
int eor = 0;
for (int i = 0; i < ints.length; i++) {
eor ^= ints[i];
}
System.out.println(eor);
3 把一个int值的最右侧1提取出来
// 0111
int a = 7;
/**
~a 取反 0111 取反 1000
~a +1 1001
a & (~a + 1) 0001
*/
int b = a & (~a + 1)
4一个数组中有两个数出现了奇数次,其他出现偶数次,找到并打印这两个数
// 第一种方法: 使用map计数 比较简单 不进行演示
// 缺点: 需要额外开辟空间map
// 第二种方法:通过位运算
int a = 0;
for (int i = 0; i < ints.length; i++) {
// 结果为 a ^ b 因为a,b不为0 所以a^b != 0
a ^= ints[i];
}
int var2 = a & (~a + 1);
int b = 0;
for (int i = 0; i < ints.length; i++) {
if ((var2 & ints[i]) == 0) {
b ^= ints[i];
}
}
System.out.println(b + "-" + (b ^ a));
二 队列和栈
1 题目
1 实现一个特殊的栈,在基础的功能上实现,实现返回栈中最小数据的功能。
- 要求push,pop,getMin的时间复杂度为O(1)
第一种方案:
两个栈,一个栈为正常存储数据的栈,另一个栈为存储最小数据的栈,每次入栈的时候都要和minData最顶的数据(即栈中最小的数据)进行对比,若新的数据小于栈顶的数据,则压入新的数据进入minData的栈;否则再次压入minData中最小的数据。
优点:栈顶一直记录的是最小的元素,且获取最小的元素的时间复杂度为O(1)
缺点:浪费空间,要创建和data栈一样的栈。
第二种方案:
还是第一种方案的模型,只不过minData栈只会压入新元素小于等于minData栈顶元素的元素。
2 如何用栈结构实现队列结构
使用两个栈,一个push栈,一个pop栈,push的时候讲数据压入push栈,poll的时候将pop栈中的数据弹出,pop栈的数据从push栈获取。注意事项:
- pop栈空时才能从push栈里获取数据
- pop从push栈中获取数据时,需要将push栈中的数据全部压入pop栈中。
3 如何用队列结构实现栈结构
使用两个队列,一个push队列,一个pop队列,push的时候把数据往push队列里插入数据,pop的时候将push的队列后面n-1个数据出站并且进入pop队列,然后push和pop队列互换身份,在将pop队列的数据pop出。用两个队列实现了栈的基本操作push和pop操作。
三 链表相关算法
1 模型
public class Node {
public int value;
public Node next;
}
2 题目
链表奇数长度返回中点,偶数长度返回上中点
利用快慢指针,慢指针一次前进一个节点,快指针一次前进两个节点。
/**
* 链表奇数长度返回中点,偶数长度返回上中点
*
* @param node1
* @return
*/
private static Node getMidUp(Node node1) {
Node fast = node1;
Node slow = node1;
while (fast != null && fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
链表奇数长度返回中点,偶数长度返回下中点
/**
* 链表奇数长度返回中点,偶数长度返回下中点
*
* @param node1
* @return
*/
private static Node getMidDown(Node node1) {
Node fast = node1;
Node slow = node1;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
判断一个链表是否有环
/**
* 判断一个链表是否有环,有环则返回一个入环节点
* 简单实现,利用set
*
* 注意事项:
* 不建议Node类重写Equels和HashCode,因为有环的情况下会导致栈溢出
* @param head
* @return
*/
public static Node isRing(Node head) {
HashSet<Node> nodesSet = new HashSet<>();
Node cur = head;
while (cur != null) {
boolean contains = nodesSet.contains(cur);
if (contains) {
return cur;
}
nodesSet.add(cur);
cur = cur.next;
}
return null;
}
/**
* 判断一个链表是否有环,有环则返回一个入环节点
* 用快指针和慢指针
*
* @param head
* @return
*/
public static Node isRingP(Node head) {
if (head == null || head.next == null) {
return null;
}
Node fast = head;
Node slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
fast = head;
break;
}
}
if (fast == head) {
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
return null;
}
给定两个可能有环也可能无环的单链表,头结点为head1和head2。实现一个函数若两个链表相交则返回相交的第一个节点,若不相交则返回null;两链表长度为n,要求时间复杂度为O(n),额外空间复杂度为O(1)。
解题思路:
① 两个都没有环的情况下,可能会存在交叉也可能没有
② 一个有环一个无环的情况下,不可能相交
③ 两个都有环的情况下,可能相交,也可能不相交
① 两个都有环,但不相交
② 在入环节点之前相交
③ 在入环节点之后相交
/**
* 两个无环链表判断是否相交,相交则返回相交点,不相交则返回null
*
* @param head1
* @param head2
* @return
*/
private static Node bothNoRing(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
cur1 = cur1.next;
n++;
}
while (cur2.next != null) {
cur2 = cur2.next;
n--;
}
// 最后一个节点不是公共节点,则不是相交
if (cur1 != cur2) {
return null;
}
// 长的位cur1
cur1 = n > 0 ? head1 : head2;
// 段的是cur2
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
/**
* 判断两个链表是否是有相交
* ① 两个都没有环的情况下,可能会存在交叉也可能没有
* ② 一个有环一个无环的情况下,不可能相交
* ③ 两个都有环的情况下,可能相交,也可能不相交
* ① 两个都有环,但不相交
* ② 在入环节点之前相交
* ③ 在入环节点之后相交
*
* @param head1
* @param head2
* @return
*/
public static Node isIntersect(Node head1, Node head2) {
// 先看是否都有环
Node ringP1 = isRingP(head1);
Node ringP2 = isRingP(head2);
/**一个有环一个没环,那就不可能相交*/
if (ringP1 == null && ringP2 != null || ringP1 != null && ringP2 == null) {
return null;
}
/**先看两个都无环的情况*/
if (ringP1 == null && ringP2 == null) {
return bothNoRing(head1, head2);
}
/**两个都有环*/
return bothRing(head1, ringP1, head2, ringP2);
}
/**
* 两个链表都有环的情况下,有交点则返回交点,无相交则返回null
*
* @param head1
* @param ringP1
* @param head2
* @param ringP2
* @return
*/
private static Node bothRing(Node head1, Node ringP1, Node head2, Node ringP2) {
// 在入环节点之前是否相交
if (ringP1 == ringP2) {
// 说明在入环节点之前相交了 退化成无环链表的交点问题
return bothNoRing(head1, head2, ringP1);
} else {
// 在入环节点之后相交
return intersectAfterRing(ringP1, ringP2);
}
}
/**
* 在入环节点之后相交
*
* @param ringP1
* @param ringP2
* @return
*/
private static Node intersectAfterRing(Node ringP1, Node ringP2) {
Node cur1 = ringP1.next;
while (cur1 != ringP1) {
if (cur1 == ringP2) {
return cur1;
}
}
return null;
}
/**
* 两个无环链表判断是否相交,相交则返回相交点,不相交则返回null
*
* @param head1
* @param head2
* @return
*/
private static Node bothNoRing(Node head1, Node head2, Node tail) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1 != tail) {
cur1 = cur1.next;
n++;
}
while (cur2 != tail) {
cur2 = cur2.next;
n--;
}
// 最后一个节点不是公共节点,则不是相交
if (cur1 != cur2) {
return null;
}
// 长的位cur1
cur1 = n > 0 ? head1 : head2;
// 段的是cur2
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}