新加进来的数,现在停在了index位置,请依次往上移动,移动到0位置,或者比自己父亲小,停止。时间复杂度O(logN)) 跟高度有关
private void heapInsert (int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap (arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
先根节点的值记录,后与最下面的子节点交换,再将heapSize - 1。
从index位置(index交换后应该是来到了根节点),往下看,不断地下沉,我较大的子节点都不再比我大或者没有子节点时候停止。时间复杂度O(logN))
private void heapify (int[] arr, int index, int heapSize) {
left = index * 2 + 1;
while (left > heapSize) { // 看是否越界
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[index] > arr[largest] ? index : largest;
if (largest == index) {
break;
}
swap (arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
public static void main (String[] args) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
heap.add (5);
heap.add (3);
System.out.println (heap.peek ());
while (! heap.isEmpty ()) {
System.out.println (heap.poll ());
}
}
public static class MyComparator implements Comparator<Integer> {
@Override
public int compare (Integer o1, Interger o2) {
return o2 - o1;
}
}
public static void main (String[] args) {
PriorityQueue<Integer> heap = new PriorityQueue<>(new MyComparator ());
heap.add (5);
heap.add (3);
heap.add (7);
heap.add (0);
heap.add (5);
heap.add (3);
heap.add (7);
heap.add (0);
while (!heap.isEmpty ()) {
System.out.println (heap.poll ());
}
}
public static void heapSort (int[] arr) {
if (arr == 0 || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert (arr, i);
}
int heapSize = arr.length;
swap (arr, 0, --heapSize);
while (headSize > 0) {
heapify (arr, 0, heapSize);
swap (arr, 0, --heapSize);
}
}
太难了,改天再研究!
public static void sortedArrDistanceLessK (int[] arr, int k) {
if (k == 0) {
return;
}
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for (; index <= Math.min (arr.length - 1, k - 1); 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 ();
}
}
给定很多线段,每个线段都有两个数[start, end],表示线段开始位置和结束位置,左右都是闭区间。规定:
1)线段的开始和结束位置一定都是整数值
2)线段重合区域的长度必须>=1
返回线段最多重合区域中,包含了几条线段
随机生成线段
public static int[][] generateLines(int N, int L, int R) {
int size = (int) (Math.random() * N) + 1;
int[][] ans = new int[size][2];
for (int i = 0; i < size; i++) {
int a = L + (int) (Math.random() * (R - L + 1));
int b = L + (int) (Math.random() * (R - L + 1));
if (a == b) {
b = a + 1;
}
ans[i][0] = Math.min(a, b);
ans[i][1] = Math.max(a, b);
}
return ans;
}
思路1
从最小长度的线段开始,计算在1.5-2.5中间有多少线段重合记录个数cnt,2.5-3.5之前有多少线段重合记录个数,然后3.5-4.5依次计算…直到最大值,最大的cnt为所求结果
public static int maxCover1 (int[][] lines) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < lines.length; i++) {
min = Math.min (min, lines[i][0]);
max = Math.max (max, lines[i][1]);
}
int cover = 0;
for (double p = min + 0.5; p < max; p += 1) {
int cur = 0;
for (int i = 0; i < lines.length; i++) {
if (lines[i][0] < p && lines[i][1] > p) {
cur++;
}
}
cover = Math.max (cover, cur);
}
return cover;
}
思路2
1.先将所有线段开始位置从小到大进行排序,再准备一个小根堆P,存放线段的结束位置
2.将线段1的开始位置a与小根堆P里的数据进行比较,如果有小于线段1的开始位置a则将P中小于的数字全部弹出,再将线段1的结束位置b放入P中,如果都大于线段1的开始位置,则不用弹出直接放入结束位置数据,然后进行小根堆排序,并记录小根堆里的数据个数ans
3.按照线段大小依次进行2步骤,最后将最大ans返回即为所求
public static int maxCover2 (int[][] m) {
public static int maxCover2(int[][] m) {
Line[] lines = new Line[m.length];
for (int i = 0; i < m.length; i++) {
lines[i] = new Line(m[i][0], m[i][1]);
}
Arrays.sort(lines, new StartComparator());
// 小根堆,每一条线段的结尾数值,使用默认的
PriorityQueue<Integer> heap = new PriorityQueue<>();
int max = 0;
for (int i = 0; i < lines.length; i++) {
// lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出
while (!heap.isEmpty () && heap.peek() <= lines[i].start) {
heap.poll ();
}
heap.add(lines[i].end);
max = Math.max(max, heap.size());
}
return max;
}
public static class Line {
public int start;
public int end;
public Line(int s, int e) {
start = s;
end = e;
}
}
public static class EndComparator implements Comparator<Line> {
@Override
public int compare (Line o1, Line o2) {
return o1.end - o2.end;
}
}
public class HeapGreater<T> {
// T 为非基础类型,或者包一层Inner
private ArrayList<T> heap; // 用Array List数组存放堆结构
private HashMap<T, Integer> indexMap; // 存放每个数据的位置,反向索引
private int heapSize; // 堆大小
private Comparator<? super T> comp; // 比较器
// 构造方法,只需要传入我们所需要的比较器,剩下的初始化
public HeapGreater(Comparator<T> c) {
heap = new ArrayList<>();
indexMap = new HashMap<>();
heapSize = 0;
comp = c;
}
// 判断堆是否为空
public boolean isEmpty() {
return heapSize == 0;
}
// 返回堆的大小
public int size() {
return heapSize;
}
// 堆中是否包含元素obj
public boolean contains(T obj) {
return indexMap.containsKey(obj);
}
// 得到堆顶元素
public T peek() {
return heap.get(0);
}
// 加入元素并进行排序
public void push(T obj) {
heap.add(obj);
indexMap.put(obj, heapSize);
heapInsert(heapSize++);
}
// 弹出堆顶元素并进行重新排序
public T pop() {
T ans = heap.get(0);
swap(0, heapSize - 1);
indexMap.remove(ans);
heap.remove(--heapSize);
heapify(0);
return ans;
}
// 去除指定元素obj,并进行重新排序
public void remove(T obj) {
T replace = heap.get(heapSize - 1);
int index = indexMap.get(obj);
indexMap.remove(obj);
heap.remove(--heapSize);
if (obj != replace) {
heap.set(index, replace);
indexMap.put(replace, index);
resign(replace);
}
}
// 对obj进行堆排序
public void resign(T obj) {
heapInsert(indexMap.get(obj));
heapify(indexMap.get(obj));
}
// 请返回堆上的所有元素
public List<T> getAllElements() {
List<T> ans = new ArrayList<>();
for (T c : heap) {
ans.add(c);
}
return ans;
}
// 对加入元素进行排序
private void heapInsert(int index) {
while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void heapify(int index) {
int left = index * 2 + 1;
while (left < heapSize) {
int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left;
best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index;
if (best == index) {
break;
}
swap(best, index);
index = best;
left = index * 2 + 1;
}
}
private void swap(int i, int j) {
T o1 = heap.get(i);
T o2 = heap.get(j);
heap.set(i, o2);
heap.set(j, o1);
indexMap.put(o2, i);
indexMap.put(o1, j);
}
}
给定一个整型数组,int[] arr ;和一个布尔类型数组,boolean[] op,两个数组一定等长,假设长度为N,arr[i]表示客户编号, op[i]表示客户操作
arr = [ 3,3,1,2,1, 2,5…
op = [ T ,T,T, T,F,T,F…
依次表示:3用户购买了一件商品,3用户购买了一件商品,1用户购买了一件商品,2用户购买了一件商品,1用户退货了一件商品,2用户购买了一件商品,5用户退货了一件商品…
一对arr[i]和op[i]就代表一个事件:
用户号为arr[i],op[i] == T 就代表这个用户购买了一件商品
op[i] == F就代表这个用户退货了一件商品
现在你作为电商平台负责人,你想在每一个事件到来的时候,都给购买次数最多的前K名用户颁奖。
所以每个事件发生后,你都需要一个得奖名单(得奖区)。
得奖系统的规则:
1,如果某个用户购买商品数为0,但是又发生了退货事件,则认为该事件无效, 得奖名单和上一个事件发生一致,比如例子中的5用户
2,某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1
3,每次都是最多K个用户得奖,K也为传入的参数
如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果
4,得奖系统分为得奖区和候选区,任何用户只要购买数>0,一定在这两个区域中的一个
5,购买数最大的前K名用户进入得奖区,在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区
6,如果购买数不足以进入得奖区的用户,进入候选区
7,如果候选区购买数最多的用户,已经足以进入得奖区,
该用户就会替换得奖区中购买数最少的用户(大于才能替换)
如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户
如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户
8, 候选区和得奖区是两套时间,因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没,
从得奖区出来进入候选区的用户,得奖区时间删除,
进入候选区的时间就是当前事件的时间(可 以理解为arr[i]和op[i]中的i)
从候选区出来进入得奖区的用户,候选区时间删除,
进入得奖区的时间就是当前事件的时间(可 以理解为arr[i]和op[i]中的i)
9,如果某用户购买数 == 0,不管在哪个区域都离开,区域时间删除,离开是指彻底离开,哪个区域也不会找到该用户
如果下次该用户又发生购买行为,产生 > 0的购买数,会再次根据之前规则回到某个区域中,进入区域的时间重记
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
public class Code02_EveryStepShowBoss {
public static class Customer {
public int id;
public int buy;
public int enterTime;
public Customer(int v, int b, int o) {
id = v;
buy = b;
enterTime = 0;
}
}
public static class CandidateComparator implements Comparator<Customer> {
@Override
public int compare(Customer o1, Customer o2) {
return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime);
}
}
public static class DaddyComparator implements Comparator<Customer> {
@Override
public int compare(Customer o1, Customer o2) {
return o1.buy != o2.buy ? (o1.buy - o2.buy) : (o1.enterTime - o2.enterTime);
}
}
public static class WhosYourDaddy {
private HashMap<Integer, Customer> customers;
private HeapGreater<Customer> candHeap;
private HeapGreater<Customer> daddyHeap;
private final int daddyLimit;
public WhosYourDaddy(int limit) {
customers = new HashMap<Integer, Customer>();
candHeap = new HeapGreater<>(new CandidateComparator());
daddyHeap = new HeapGreater<>(new DaddyComparator());
daddyLimit = limit;
}
// 当前处理i号事件,arr[i] -> id, buyOrRefund
public void operate(int time, int id, boolean buyOrRefund) {
if (!buyOrRefund && !customers.containsKey(id)) {
return;
}
if (!customers.containsKey(id)) {
customers.put(id, new Customer(id, 0, 0));
}
Customer c = customers.get(id);
if (buyOrRefund) {
c.buy++;
} else {
c.buy--;
}
if (c.buy == 0) {
customers.remove(id);
}
if (!candHeap.contains(c) && !daddyHeap.contains(c)) {
if (daddyHeap.size() < daddyLimit) {
c.enterTime = time;
daddyHeap.push(c);
} else {
c.enterTime = time;
candHeap.push(c);
}
} else if (candHeap.contains(c)) {
if (c.buy == 0) {
candHeap.remove(c);
} else {
candHeap.resign(c);
}
} else {
if (c.buy == 0) {
daddyHeap.remove(c);
} else {
daddyHeap.resign(c);
}
}
daddyMove(time);
}
public List<Integer> getDaddies() {
List<Customer> customers = daddyHeap.getAllElements();
List<Integer> ans = new ArrayList<>();
for (Customer c : customers) {
ans.add(c.id);
}
return ans;
}
private void daddyMove(int time) {
if (candHeap.isEmpty()) {
return;
}
if (daddyHeap.size() < daddyLimit) {
Customer p = candHeap.pop();
p.enterTime = time;
daddyHeap.push(p);
} else {
if (candHeap.peek().buy > daddyHeap.peek().buy) {
Customer oldDaddy = daddyHeap.pop();
Customer newDaddy = candHeap.pop();
oldDaddy.enterTime = time;
newDaddy.enterTime = time;
daddyHeap.push(newDaddy);
candHeap.push(oldDaddy);
}
}
}
}
public static List<List<Integer>> topK(int[] arr, boolean[] op, int k) {
List<List<Integer>> ans = new ArrayList<>();
WhosYourDaddy whoDaddies = new WhosYourDaddy(k);
for (int i = 0; i < arr.length; i++) {
whoDaddies.operate(i, arr[i], op[i]);
ans.add(whoDaddies.getDaddies());
}
return ans;
}
// 干完所有的事,模拟,不优化
public static List<List<Integer>> compare(int[] arr, boolean[] op, int k) {
HashMap<Integer, Customer> map = new HashMap<>();
ArrayList<Customer> cands = new ArrayList<>();
ArrayList<Customer> daddy = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
int id = arr[i];
boolean buyOrRefund = op[i];
if (!buyOrRefund && !map.containsKey(id)) {
ans.add(getCurAns(daddy));
continue;
}
// 没有发生:用户购买数为0并且又退货了
// 用户之前购买数是0,此时买货事件
// 用户之前购买数>0, 此时买货
// 用户之前购买数>0, 此时退货
if (!map.containsKey(id)) {
map.put(id, new Customer(id, 0, 0));
}
// 买、卖
Customer c = map.get(id);
if (buyOrRefund) {
c.buy++;
} else {
c.buy--;
}
if (c.buy == 0) {
map.remove(id);
}
// c
// 下面做
if (!cands.contains(c) && !daddy.contains(c)) {
if (daddy.size() < k) {
c.enterTime = i;
daddy.add(c);
} else {
c.enterTime = i;
cands.add(c);
}
}
cleanZeroBuy(cands);
cleanZeroBuy(daddy);
cands.sort(new CandidateComparator());
daddy.sort(new DaddyComparator());
move(cands, daddy, k, i);
ans.add(getCurAns(daddy));
}
return ans;
}
public static void move(ArrayList<Customer> cands, ArrayList<Customer> daddy, int k, int time) {
if (cands.isEmpty()) {
return;
}
// 候选区不为空
if (daddy.size() < k) {
Customer c = cands.get(0);
c.enterTime = time;
daddy.add(c);
cands.remove(0);
} else { // 等奖区满了,候选区有东西
if (cands.get(0).buy > daddy.get(0).buy) {
Customer oldDaddy = daddy.get(0);
daddy.remove(0);
Customer newDaddy = cands.get(0);
cands.remove(0);
newDaddy.enterTime = time;
oldDaddy.enterTime = time;
daddy.add(newDaddy);
cands.add(oldDaddy);
}
}
}
public static void cleanZeroBuy(ArrayList<Customer> arr) {
List<Customer> noZero = new ArrayList<Customer>();
for (Customer c : arr) {
if (c.buy != 0) {
noZero.add(c);
}
}
arr.clear();
for (Customer c : noZero) {
arr.add(c);
}
}
public static List<Integer> getCurAns(ArrayList<Customer> daddy) {
List<Integer> ans = new ArrayList<>();
for (Customer c : daddy) {
ans.add(c.id);
}
return ans;
}
实现两种功能:
哈希表的增删改查时间复杂度不一定是O(1),看单样本长度
前缀树形成的时间复杂度O(M),M为字符串总长度,查询字符串出现次数时间复杂度为O(K),K为字符串长度,同理查询前缀的时间复杂度也跟被查询字符串的长度有关。
public static class Node1 {
public int pass;
public int end;
public Node1[] nexts;
// char tmp = 'b' (tmp - 'a')
public Node1() {
pass = 0;
end = 0;
// 0 a,1 b,2 c,.. ..,,25 z
// nexts[i] == null i方向的路不存在, nexts[i] != null i方向的路存在
nexts = new Node1[26]; // 26个字母长度,存放node节点的数组
}
}
public static class Trie1 {
private Node1 root;
public Trie1() {
root = new Node1();
}
// 生成前缀树
public void insert (String word) {
if (word == null) {
return;
}
char[] str = word.toCharArray (); // toCharArray()方法返回字符串数组
Node1 node = root;
node.pass++;
int path = 0;
for (int i = 0; i < str.length; i++) { // 从左往右遍历字符
path = str[i] - 'a'; // 由字符,对应成走向哪条路
if (node.nexts[path] == null) {
node.nexts[path] = new Node1();
}
node = node.nexts[path]; // node 向下沉
node.pass++;
}
node.end++;
}
// 删除某个字符串,防止内存泄漏
public void delete (String word) {
if (search (word) != 0) {
char[] chs = word.toCharArray ();
Node1 node = root;
node.pass--;
int path = 0;
for (int i = 0; i < chs.length; i++) {
path = chs[i] - 'a';
if (--node.nexts[path].pass == 0) {
node.nexts[path] = null;
return;
}
node = node.nexts[path];
}
node.end--;
}
}
// word这个单词之前加入过几次
public int search (String word) {
if (word == null) {
return 0;
}
char[] chs = word.toCharArray ();
Node1 node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.end;
}
// 所有加入的字符串中,有几个是以pre这个字符串作为前缀的
public int prefixNumber (String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray ();
Node1 node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.pass;
}
}
// only for 0~200 value
public static void countSort (int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max (max, arr[i]);
}
int[] bucket = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
int i = 0;
for (int j = 0; j < bucket.length; j++) {
while (bucket[j]-- > 0) {
arr[i++] = j;
}
}
}
条件:非负的,能用十进制表示的(如果是负数,找到最小的数字,然后加上这个数的正数,最后在减去,但是有边界问题)
思路
准备0-9个桶,先取所有数据的个位数,依次放入桶中,然后按顺序从小到大弹出,在将数据中十位数依次放入桶中,再按从小到大弹出,最后按照百位数放入桶中,同理,最后弹出的顺序即为所求(总结:依次放进桶,依次排序,但以高位为主)
// only for no-negative value
public static void radixSort (int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort (arr, 0, arr.length - 1, maxbits (arr));
}
// 计算最大数是几位
public static int maxbits (int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max (max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
// 对任意数组下标进行排序
// arr[L..R]排序 , 最大值的十进制位数digit
public static void radixSort (int[] arr, int L, int R, int digit) {
final int radix = 10;
int i = 0, j = 0;
// 有多少个数准备多少个辅助空间
int[] help = new int[R - L + 1];
for (int d = 1; d <= digit; d++) { // 有多少位就进出几次
// 10个空间
// count[0] 当前位(d位)是0的数字有多少个
// count[1] 当前位(d位)是(0和1)的数字有多少个
// count[2] 当前位(d位)是(0、1和2)的数字有多少个
// count[i] 当前位(d位)是(0~i)的数字有多少个
int[] count = new int[radix]; // count[0..9]
// count数组,例如[1,3,2,0,0,0],0结尾的有一个,1结尾有三个,2结尾有两个
for (i = L; i <= R; i++) {
// 103 1 3
// 209 1 9
j = getDigit (arr[i], d);
count[j]++;
}
// count'数组,例如[1,4,6,6,6,6],依次相加
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
// 从右到左依次放入
for (i = R; i >= L; i--) {
j = getDigit (arr[i], d);
help[count[j] - 1] = arr[i];
count[j]--;
}
// 从右到左依次放入
for (i = L, j = 0; i <= R; i++, j++) {
arr[i] = help[j];
}
}
}
public static int getDigit (int x, int d) {
return ((x / ((int) Math.pow (10, d - 1))) % 10);
}
计数排序和基数排序
1)一般来讲,计数排序要求,样本是整数,且范围比较窄
2)一般来讲, 基数排序要求,样本是10进制的正整数
一旦要求稍有升级,改写代价增加是显而易见的
排序算法的稳定性
稳定性是指同样大小的样本再排序之后不会改变相对次序
对基础类型来说,稳定性毫无意义
对非基础类型来说,稳定性有重要意义
有些排序算法可以实现成稳定的,而有些排序算法无论如何都实现不成稳定的
时间复杂度 | 额外空间复杂度 | 稳定性 | |
---|---|---|---|
选择排序 | O(N^2) | O(1) | 无 |
冒泡排序 | O(N^2) | O(1) | 有 |
插入排序 | O(N^2) | O(1) | 有 |
归并排序 | O(N*logN) | O(N) | 有 |
随机排序 | O(N^logN) | O(logN) | 无 |
堆排序 | O(N^logN) | O(1) | 无 |
计数排序 | O(N) | O(M) | 有 |
基数排序 | O(N) | O(N) | 有 |
排序算法总结
1)不基于比较的排序,对样本数据有严格要求,不易改写
2) 基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
3)基于比较的排序,时间复杂度的极限是O(NlogN)
4)时间复杂度O(NlogN)、 额外空间复杂度低于O(N)、 且稳定的基于比较的排序是不存在的。
5) 为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
快慢指针练习(单链表)
用快慢指针和数个数两种方法实现下面问题:
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
public static class Node {
public int value;
public Node next;
public Node(int v) {
value = v;
}
}
// head 头
public static Node midOrUpMidNode (Node head) {
if (head == null || head.next == null || head.next.next == null) {
return head;
}
// 链表有3个点或以上
Node slow = head.next;
Node fast = head.next.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static Node midOrDownMidNode (Node head) {
if (head == null || head.next == null) {
return head;
}
Node slow = head.next;
Node fast = head.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static Node midOrUpMidPreNode (Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head;
Node fast = head.next.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static Node midOrDownMidPreNode (Node head) {
if (head == null || head.next == null) {
return null;
}
if (head.next.next == null) {
return head;
}
Node slow = head;
Node fast = head.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static Node right1 (Node head) {
if (head == null) {
return null;
}
Node cur = head;
ArrayList<Node> arr = new ArrayList<>();
while (cur != null) {
arr.add (cur);
cur = cur.next;
}
return arr.get ((arr.size () - 1) / 2);
}
public static Node right2 (Node head) {
if (head == null) {
return null;
}
Node cur = head;
ArrayList<Node> arr = new ArrayList<>();
while (cur != null) {
arr.add (cur);
cur = cur.next;
}
return arr.get (arr.size () / 2);
}
public static Node right3 (Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node cur = head;
ArrayList<Node> arr = new ArrayList<>();
while (cur != null) {
arr.add (cur);
cur = cur.next;
}
return arr.get ((arr.size () - 3) / 2);
}
public static Node right4 (Node head) {
if (head == null || head.next == null) {
return null;
}
Node cur = head;
ArrayList<Node> arr = new ArrayList<>();
while (cur != null) {
arr.add (cur);
cur = cur.next;
}
return arr.get ((arr.size () - 2) / 2);
}
面试时链表解题的方法论
1)对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
2)对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
链表面试题常用数据结构和技巧
1)使用容器(哈希表、数组等)
2)快慢指针
常见面试题
1.给定一个单链表的头节点head,请判断该链表是否为回文结构(正念和反念一样:12321)。
1)哈希表方法特别简单(笔试用)
2)改原链表的方法就需要注意边界了(面试用)
思路:
1)用栈结构,遍历一个弹出一个,比较是否一样。
2)奇数个时,取中间数,将它下一个指针指向null,再将后面的数指向取反,然后左右头节点同时向中间遍历比较,直到遍历为空,偶数个/2-1个设为null,同理,直到有一个为null停止,返回yes or no前把链表复原。
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
// need n extra space,利用栈容器
public static boolean isPalindrome1 (Node head) {
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;
}
// need O(1) extra space
public static boolean isPalindrome3 (Node head) {
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
}
// n1 中点
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.将单向链表按某值划分成左边小、中间相等、右边大的形式
1)把链表放入数组里,在数组上做partition (笔试用)
2) 分成小、中、大三部分,再把各个部分之间串起来(面试用)
思路:
用六个引用,小头、小尾、等头、等尾、大头、大尾分别存放小区的头节点、小区的尾节点… …(存在尾节点是为了保证排序的稳定性)最后将小尾与等头,等尾与大头相连。
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 mH = null; // big head
Node mT = null; // big tail
Node next = null; // save next node
// every node distributed to three lists
while (head != null) {
next = head.next;
head.next = null;
if (head.value < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (mH == null) {
mH = head;
mT = head;
} else {
mT.next = head;
mT = head;
}
}
head = next;
}
// 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头
if (sT != null) { // 如果有小于区域
sT.next = eH;
eT = eT == null ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT
}
// 下一步,一定是需要用eT 去接 大于区域的头
// 有等于区域,eT -> 等于区域的尾结点
// 无等于区域,eT -> 小于区域的尾结点
// eT 尽量不为空的尾巴节点,既没有小于也没有等于
if (eT != null) { // 如果小于区域和等于区域,不是都没有
eT.next = mH;
}
return sH != null ? sH : (eH != null ? eH : mH);
}
public static void printLinkedList (Node node) {
System.out.print ("Linked List: ");
while (node != null) {
System.out.print (node.value + " ");
node = node.next;
}
System.out.println ();
}
3.一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(int val) { value = val;}
rand指针是单链表节点结构中新增的指针,rand 可能指向链表中的任意-个节点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
[要求]
时间复杂度0(N),额外空间复杂度0(1)
思路
用may结构,key存放老节点,value存放复制老节点的新节点,目的是每一个老节点都能找到对应的新节点。然后建立新节点的next和random节点的时候,去查询key中老节点的next与random
public static class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
public static Node copyRandomList1 (Node head) {
// key 老节点
// value 新节点
HashMap<Node, Node> map = new HashMap<Node, Node>();
Node cur = head;
while (cur != null) {
map.put (cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
while (cur != null) {
// cur 老
// map.get(cur) 新
// 新.next -> cur.next克隆节点找到
map.get (cur).next = map.get (cur.next);
map.get (cur).random = map.get (cur.random);
cur = cur.next;
}
return map.get (head);
}
public static Node copyRandomList2 (Node head) {
if (head == null) {
return null;
}
Node cur = head;
Node next = null;
// 1 -> 2 -> 3 -> null
// 1 -> 1' -> 2 -> 2' -> 3 -> 3'
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.val);
cur.next.next = next;
cur = next;
}
cur = head;
Node copy = null;
// 1 1' 2 2' 3 3'
// 依次设置 1' 2' 3' random指针
while (cur != null) {
next = cur.next.next;
copy = cur.next;
copy.random = cur.random != null ? cur.random.next : null;
cur = next;
}
Node res = head.next;
cur = head;
// 老 新 混在一起,next方向上,random正确
// next方向上,把新老链表分离
while (cur != null) {
next = cur.next.next;
copy = cur.next;
cur.next = next;
copy.next = next != null ? next.next : null;
cur = next;
}
return res;
}
4.给定两个可能有环也可能无环的单链表,头节点head1和head2。
请实现一个函数,如果两个链表相交,请返回相交的第个节点。
如果不相交,返回null
[要求]
如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复
杂度请达到O(1)。