0.特殊
0.1 异或
0.1.1 用异或做交换
void swap(int& a, int& b){
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
- 原理:
- N ^ N == 0
- N ^ 0 == N
- (A ^ B) ^ C == A ^ (B ^ C)
- A ^ B == B ^ A
- 所以:
- 当:a = a ^ b
- 则:b = a ^ b = a ^ b ^ b = a
- 则:a = a ^ b ^ a = b
- 注意,a和b不可以是同一块内存
0.1.2 用异或找数
题目:1)一个整数数组,只有一个数字出现了奇数次,其他数字都出现了偶数次,找出这个数
int find(vector<int>& arr){
int res = 0;
for(int i = 0; i < arr.size(); i++) res ^= arr[i];
return res;
}
题目:2)一个整数数组,只有两个个数字出现了奇数次,其他数字都出现了偶数次,找出这个数
vector<int>& find(vector<int>& arr){
int res1 = 0;
for(int i = 0; i < arr.size(); i++) res1 ^= arr[i];
int m = res1 & (~res1 + 1);//提取二进制数最右侧的1,这一位俩个数不一样
int res2 = 0;
for(int i = 0; i < arr.size(); i++){
if((arr[i] & m)==0) res2 ^= arr[i];//找到了其中一个数
}
res1 = res1 ^ res2;//根据这一个数和两者的异或,得到另一个数
return {res1,res2};
}
1.排序算法
1.1 简单排序
1.1.1 选择排序
- 时间O(N2) 、空间O(1)、不稳
void SelectionSort(vector<int>& data){
for(int i = 0; i< data.size() - 1; i++){
int min = i;
for(int j = i + 1; j < data.size(); j++){
if(data[j] < data[min]) min = j;
}
swap(data,i,min);
}
}
1.1.2 冒泡排序
- 时间O(N2) 、空间O(1)、稳
void BubbleSort(vector<int>& data){
for(int i = data.size() - 1; i > 0; i--){
for(int j = 0; j < i; j++){
if(data[j] > data[j+1]) swap(data,j,j+1);
}
}
}
1.1.3 插入排序
- 时间O(N2) 、空间O(1)、稳
- 样本数少(<60)一般使用插入排序
void InsertionSort(vector<int>& data){
for(int i = 1; i < data.size(); i++){
for(int j = i; j > 0 && data[j] < data[j-1]; j--){
swap(data,j,j-1);
}
}
}
1.2 特殊排序
1.2.1 归并排序
- 时间O(NlogN)、空间O(N)、稳
- 升级版本是TimSort
void MergeSort(vector<int>& data){
Split(data,0,data.size()-1);
}
void Split(vector<int>& data, int leftPos, int rightPos){
if(leftPos == rightPos) return;
int midPos = 1 + leftPos + (rightPos - leftPos) / 2;
Split(data,leftPos,midPos-1);
Split(data,midPos,rightPos);
Merge(data,leftPos,midPos,rightPos);
}
void Merge(vector<int>& data, int leftPtr, int rightPtr, int endPos){
vector<int> res(endPos - leftPtr + 1);
int i = leftPtr; int j = rightPtr; int k = 0;
while(i <= rightPtr-1 && j <= endPos) res[k++] = data[i] <= data[j] ? data[i++] : data[j++];
while(i <= rightPtr-1) res[k++] = data[i++];
while(j <= endPos) res[k++] = data[j++];
for(int m = 0; m < res.size(); m++) data[leftPtr+m] = res[m];
}
1.2.2 快速排序
- 时间O(NlogN) 、空间O(logN)、不稳
- 升级版是:双轴快排
void QuickSort(vector<int>& data){
Split(data,0,data.size()-1);
}
void Split(vector<int>& data, int leftPos, int rightPos){
if(leftPos >= rightPos) return;
int midPos = Partition(data,leftPos,rightPos);
Split(data,leftPos,midPos-1);
Split(data,midPos+1,rightPos);
}
int Partition(vector<int>& data, int leftPos, int rightPos){
int lptr = leftPos; int rptr = rightPos-1; int pivot = data[rightPos];
while(lptr <= rptr){
while(lptr <= rptr && data[lptr] <= pivot) lptr++;
while(lptr <= rptr && data[rptr] > pivot) rptr--;
if(lptr < rptr) swap(data,lptr,rptr);
}
swap(data,lptr,rightPos);
return lptr;
}
1.2.3 希尔排序
- 时间O(N1.3) 、空间O(1)、不稳
void ShellSort(vector<int>& data){
for(int gap = data.size()/2; gap > 0; gap /= 2)
{
for(int i = gap; i < data.size(); i++)
{
for(int j = i; j > gap-1 && data[j] < data[j-gap]; j -= gap){
swap(data,j,j-gap);}
}
}
}
1.3 桶排序
1.3.1 计数排序
时间O(N+K)、空间O(N+K)、稳
void CountingSort(vector<int>& data)
{
int min = 0; int max = 0;
for(int i = 0; i < data.size(); i++){
if(data[i] > max) max = data[i];
if(data[i] < min) min = data[i];
}
vector<int> count(max - min);
vector<int> res(data.size());
for(int i = 0; i < data.size(); i++) count[data[i]-min]++;
for(int i = 1; i < count.size(); i++) count[i] += count[i-1];
for(int i = data.size()-1; i >= 0; i--) res[--count[data[i]-min]] = data[i];
for(int i = 0; i < data.size(); i++) data[i] = res[i];
}
1.3.2 基数排序
时间O(N*K)、空间O(N)、稳定
void RadixSort(vector<int>& data){
int max = 0,times = 0;
for (int i = 0; i < data.size(); i++) if (data[i] > max) max = data[i];
for (; max > 0; max /= 10) times++;
for (int i = 0; i < times; i++)
{
vector<int> res(data.size());
int count[10] = {};
int dev = pow(10, i);
for (int j = 0; j < data.size(); j++) count[data[j] / dev % 10]++;
for (int j = 1; j < 10; j++) count[j] += count[j - 1];
for (int j = data.size() - 1; j >= 0; j--){
res[--count[data[j] / dev % 10]] = data[j];}
for (int j = 0; j < data.size(); j++) data[j] = res[j];
}
}
1.4 堆排序
- 时间O(NlogN)、空间O(1)、不稳
void heapInsert(vector<int>& arr, int index)
{
while (arr[index] > arr[(index - 1) / 2])
{
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
void heapify(vector<int>& arr, int index, int heapSize)
{
int leftChild = 2 * index + 1;
while (leftChild < heapSize)
{
int large = leftChild + 1 < heapSize && arr[leftChild + 1] > arr[leftChild] ? leftChild + 1 : leftChild;
large = arr[large] > arr[index] ? large : index;
if (large == index) break;
swap(arr, large, index);
index = large;
leftChild = 2 * index + 1;
}
}
void HeapSort(vector<int>& arr)
{
for (int i = 0; i < arr.size(); i++) heapInsert(arr, i);
int heapSize = arr.size();
swap(arr, 0, --heapSize);
while (heapSize > 0)
{
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
1.5 题目
1.5.1 小和问题 – 改写归并排序
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return mergeSort(arr, l, mid)
+ mergeSort(arr, mid + 1, r)
+ merge(arr, l, mid, r);
}
public static int merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return res;
}
1.5.2 对基本有序数组排序 – 改写堆排序
- 每个数字都没有移动超过k步
public void sortedArrDistanceLessK(int[] arr, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for (; index < Math.min(arr.length, k); index++) {
heap.add(arr[index]);
}
int i = 0;
for (; index < arr.length; i++, index++) {
heap.add(arr[index]);
arr[i] = heap.poll();
}
while (!heap.isEmpty()) {
arr[i++] = heap.poll();
}
}
1.5.3 荷兰国旗问题 – 快排前身
public static int[] partition(int[] arr, int l, int r, int p) {
int less = l - 1;
int more = r + 1;
while (l < more) {
if (arr[l] < p) {
swap(arr, ++less, l++);
} else if (arr[l] > p) {
swap(arr, --more, l);
} else {
l++;
}
}
return new int[] {
less + 1, more - 1 };
}
1.5.4 比较器 – 自定义结构排序
public static class Student {
public String name;
public int id;
public int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
public static class IdAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
}
}
2. 链表
2.1 基本概念
- 哈希表:
- UnorderdMap – Key-Value
- UnorderdSet – value
- 主要方法:add、remove、put、get – 时间均为O(1)
- 有序表:
- OrderdMap – Key-Value
- OrderdSet – value
- 主要方法:put、get、firstkey、lastkey、floorkey、ceillingkey、remove – 时间为O(logN)
- 基础类型按照值传递、自定义类型引用传递
HashSet<Integer> hashSet1 = new HashSet<>();
hashSet1.add(3);
hashSet1.remove(3);
hashSet1.contains(3)
HashMap<String, Integer> hashMap1 = new HashMap<>();
hashMap1.put(str1, 1);
hashMap1.containsKey(str1);
hashMap1.get(str1);
TreeMap<Integer, String> treeMap1 = new TreeMap<>();
treeMap1.put(7, "我是7");
treeMap1.containsKey(5);
treeMap1.get(5);
treeMap1.firstKey();
treeMap1.lastKey();
treeMap1.floorKey(8);
treeMap1.ceilingKey(8);
treeMap1.remove(5);
- 有序表中存放自定义数据类型时,还需要提供比较器
public class Node{
public int value;
public Node next;
}//单链表
public class Node{
public int value;
public Node next;
public Node last;
}//双链表
- 基本的解题思路:
- 笔试的时候,找时间复杂度最低的方法,借助额外的数据结构来解题
- 面试的时候,还需要空间复杂度尽可能低,尽可能不借助额外的数据结构来解题
- 技巧:使用辅助数据结构、使用快慢指针
2.2 快慢指针方法
快指针 F 和慢指针 S 都从头开始,但 F 一次走两步,S 一次走一步
当 F 到达null的时候,S 到达中点
2.3 题目
2.3.1 最基本的题目
- 反转单链表、双链表
public static Node reverseList(Node head) {
// 单链表反转
Node pre = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
public static DoubleNode reverseList(DoubleNode head) {
// 双链表反转
DoubleNode pre = null;
DoubleNode next = null;
while (head != null) {
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}
- 打印链表
public static void printLinkedList(Node head) {
// 单链表打印
System.out.print("Linked List: ");
while (head != null) {
System.out.print(head.value + " ");
head = head.next;
}
System.out.println();
}
public static void printDoubleLinkedList(DoubleNode head) {
// 双链表打印
System.out.print("Double Linked List: ");
DoubleNode end = null;
while (head != null) {
// 正向打印
System.out.print(head.value + " ");
end = head;
head = head.next;
}
System.out.print("| ");
while (end != null) {
// 反向打印
System.out.print(end.value + " ");
end = end.last;
}
System.out.println();
}
- 打印两个有序链表的公共部分(归并的思路)
public static void printCommonPart(Node head1, Node head2) {
System.out.print("Common Part: ");
while (head1 != null && head2 != null) {
if (head1.value < head2.value) {
head1 = head1.next;
} else if (head1.value > head2.value) {
head2 = head2.next;
} else {
System.out.print(head1.value + " ");
head1 = head1.next;
head2 = head2.next;
}
}
System.out.println();
}
2.3.2 回文链表
如1->2->2->1为回文列表
- 方法一:
利用栈,将结点依次放入栈中,再挨个弹出与原列表逐个比较
public static boolean isPalindrome1(Node head) {
// space O(N)
Stack<Node> stack = new Stack<Node>();
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (head != null) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
public static boolean isPalindrome2(Node head) {
// space O(N/2)
if (head == null || head.next == null) {
return true;
}
Node right = head.next;
Node cur = head;
while (cur.next != null && cur.next.next != null) {
right = right.next;//慢指针
cur = cur.next.next;//快指针
}
Stack<Node> stack = new Stack<Node>();
while (right != null) {
//从中点开始压栈
stack.push(right);
right = right.next;
}
while (!stack.isEmpty()) {
//从栈顶一个一个弹出和链表从头部开始依次比较
if (head.value != stack.pop().value) {
return false;
}
head = head.next;//遍历链表
}
return true;
}
- 方法二:
利用快慢指针找到中点,将后半截反序,两个指针从两边遍历到中点,逐个比较
public static boolean isPalindrome3(Node head) {
// space O(1)
if (head == null || head.next == null) {
return true;
}
Node n1 = head;
Node n2 = head;
while (n2.next != null && n2.next.next != null) {
// find mid node
n1 = n1.next; // n1 -> mid
n2 = n2.next.next; // n2 -> end
}
n2 = n1.next; // n2 -> right part first node
n1.next = null; // mid.next -> null
Node n3 = null;
while (n2 != null) {
// right part convert
n3 = n2.next; // n3 -> save next node
n2.next = n1; // next of right node convert
n1 = n2; // n1 move
n2 = n3; // n2 move
}
n3 = n1; // n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while (n1 != null && n2 != null) {
// check palindrome
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next; // left to mid
n2 = n2.next; // right to mid
}
n1 = n3.next;
n3.next = null;
while (n1 != null) {
// recover list
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
2.3.3 单链表荷兰国旗问题
题目:给定一个单链表,一个Pivot,将链表重新排列为:小于Pivot、等于Pivot、大于Pivot三个区域
- 方法一:
建立一个结点数组,用Partition的方法来对数组里的节点重新排列,最后连接起来
public static Node listPartition1(Node head, int pivot) {
if (head == null) {
return head;
}
Node cur = head;
int i = 0;
while (cur != null) {
// 计算结点个数
i++;
cur = cur.next;
}
Node[] nodeArr = new Node[i];// 用结点个数来建立新数组
i = 0;
cur = head;
for (i = 0; i != nodeArr.length; i++) {
// 将结点依次填入数组中
nodeArr[i] = cur;
cur = cur.next;
}
arrPartition(nodeArr, pivot);// 对数组进行荷兰国旗问题处理
for (i = 1; i != nodeArr.length; i++) {
// 对排列好的结点依次连接成链表
nodeArr[i - 1].next = nodeArr[i];
}
nodeArr[i - 1].next = null;
return nodeArr[0];
}
public static void arrPartition(Node[] nodeArr, int pivot) {
// 对数组进行荷兰国旗问题处理
int small = -1;
int big = nodeArr.length;
int index = 0;
while (index != big) {
if (nodeArr[index].value < pivot) {
swap(nodeArr, ++small, index++);
} else if (nodeArr[index].value == pivot) {
index++;
} else {
swap(nodeArr, --big, index);
}
}
}
- 方法二:
使用六个指针:SH、ST、EH、ET、BH、BT 分别指向 小于区域、等于区域、大于区域的头和尾
一开始六个指针都指向null,
发现第一个满足区域条件时,该区域的H指针指向它就不动了
每发现一个满足该区域条件时,该区域的T指针指向它,该区域的各个节点相连
最后:ST->EH、ET->BH,而SH为新链表的头
要注意边界,因为某个区域可能为空
public static Node listPartition2(Node head, int pivot) {
Node sH = null; // small head
Node sT = null; // small tail
Node eH = null; // equal head
Node eT = null; // equal tail
Node bH = null; // big head
Node bT = null; // big tail
Node next = null; // save next node
// every node distributed to three lists
while (head != null) {
next = head.next;
head.next = null; // 断开连接
// H如果为空,H就要等于head,
// 否则让尾端的下一个结点连head,
// 不管怎么样,在最后都要让T等于head
if (head.value < pivot) {
// 小于区域
if (sH == null) sH = head;
else sT.next = head;
sT = head;
}
else if (head.value == pivot) {
// 等于区域
if (eH == null) eH = head;
else eT.next = head;
eT = head;
}
else {
// 大于区域
if (bH == null) bH = head;
else bT.next = head;
bT = head;
}
head = next;
}
// small and equal reconnect
if (sT != null) {
sT.next = eH;
eT = eT == null ? sT : eT;
}
// all reconnect
if (eT != null) {
eT.next = bH;
}
return sH != null ? sH : eH != null ? eH : bH;
}
2.3.4 复制特殊链表
题目:一种新的链表,除了有Node next成员外,还有一个Node rand随机指向一个节点,将该链表复制一遍
- 方法一:
用Map:key-value的查找方法,挨个对应
public static Node copyListWithRand1(Node head) {
HashMap<Node, Node> map = new HashMap<Node, Node>();
Node cur = head;
while (cur != null) {
// 把每个结点和结点的复制品放入哈希表
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
while (cur != null) {
// 遍历链表,每个链表的复制品按照对应关系连接
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
- 方法二:
在每个节点后面复制一个该节点,然后遍历,一一对应rand指针
然后逐个解开next,对应新next
public static Node copyListWithRand2(Node head) {
if (head == null) {
return null;
}
Node cur = head;
Node next = null;
// copy node and link to every node 在每个节点后插入一个自己的复制品
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
// set copy node rand 复制品的rand挨个连接对应复制品 cur.rand.next
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand.next : null;
cur = next;
}
Node res = head.next;
cur = head;
// split 复制品和原链表分离的同时,将复制品的next连对
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next != null ? next.next : null;
cur = next;
}
return res;
}
*2.3.5 一个单链表是否有环
上面为无环,下面为有环,B为入环节点
- 方法一:
用set来依次放入节点,当第一次查找能找到时,这个节点就是入环节点
- *方法二:改进的快慢指针
S 和 F 从头节点出发,若 F 到了null,则一定无环
若 S 和 F 相遇,则一定有环
此时,让 F 回到起点,S 和 F 一次走一步,它们一定会在入环节点相遇
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null; // 快慢指针起点若为空,则直接判断无环
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
// 快慢指针相遇
if (n2.next == null || n2.next.next == null) {
return null; // 发现终点说明无环
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head 快指针回到链表头
while (n1 != n2) {
// 各自都一次一步直到相遇
n1 = n1.next;
n2 = n2.next;
}
return n1;// 返回相遇的点
}
2.3.6 两个单链表相交
题目:两个单链表head1和head2,可能有环,判断它们相交与否,若相交,返回第一个相交结点
先判断是不是有环:
- 都无环,有两种情况

最低0.47元/天 解锁文章
478

被折叠的 条评论
为什么被折叠?



