【算法刷题】左神进阶班笔记

KMP

public int getIndexOf(String s, String m){
    if(s == null || m == null || m.length() < 1 || s.length() < m.length()){
        return -1;
    }
    char[] ss = s.toCharArray();
    char[] ms = m.toCharArray();
    int si = 0;
    int mi = 0;
    int[] next = getNextArray(ms);
    while (si < ss.length && mi < ms.length){
        if (ss[si] == ms[mi]){
            si++;
            mi++;
        } else if (next[mi] == -1){
            si+;
        } else {
            mi = next[mi];
        }
    }
    return mi == ms.length ? si - mi : -1;
}

public int[] getNextArray(char[] ms){
    if (ms.length == 1){
        return new int[] { -1 };
    }
    int[] next = new int[ms.length];
    next[0] = -1;
    next[1] = 0;
    int pos = 2;
    int cn = 0;
    while (pos < next.length){
        if (ms[pos - 1] == ms[cn]){
            next[pos++] = ++cn;
        } else if (cn > 0){
            cn = next[cn];
        } else {
            next[i++] = 0;
        }
    }
    return next;
}

用处:

原字符串’abcabc’,只能在后面添加字符,使得原字符串出现两次,求最短的添加字符。

e.g. ‘abcabc’ + ‘abc’ = ‘abcabcabc’,原字符串’abcabc’出现了两次

求法:

求原字符串每一个的next数组,得到原字符串最大长度后一个的next数组值。该值表明原字符串的最大可复用长度(next数组的意义),然后用原字符串长度减去最大可复用长度,得到应该添加字符的长度,即从原字符串倒数取得,添加即可。

Manacher

以O(N)的时间复杂度处理回文字符串,求最大回文子串,回文边界

public char[] manacherString(String str){
    char[] charArr = str.toCharArray();
    char[] res = new char[str.length() * 2 + 1];
    int index = 0;
    for (int i = 0; i != res.length; i++){
        res[i] = (i & 1) == 0 ? '#' : charArr[index++];
    }
    return res;
}

public int maxLcpsLength(String str){
    if (str == null || str.length() == 0){
        return 0;
    }
    char[] charArr = manacherString(Str);
    int[] pArr = new int[charArr.length];
    int index = -1;
    int pR = -1;
    int max = Integer.MIN_VALUE;
    for (int i = 0; i !=charArr.length; i++){
        pArr[i] = pR > i ? Math.min(pArr[2 * index - i], pR - i) : 1;
        while( i + pArr[i] < charArr.length && i - pArr[i] > -1){
            if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
                pArr[i]++;
            else{
                break;
            }
        }
        if (i + pArr[i] > pR){
            pR = i + pArr[i];
            index = i;
        }
        max = Math.max(max,pArr[i]);
    }
    return max - 1;
}

用处:

一个字符串,只能向后面添加字符,怎么整个串都变成回文串,要求添加字符最短。

e.g. ‘abc12321’ 应添加 ‘cba’ 变成’abc12321cba‘

即求,在包含最后一个字符情况下,最长回文串多少,前面不是的部分,逆序添加即可。

用manacher求得回文边界,发现边界与字符串最后位置,停止,求得回文中心C与有边界R,找到回文字符串,用原字符串减去,在逆序。

public String shortestEnd(String str){
    if (str == null || str.length() == 0){
        return null;
    }
    char[] charArr = manacherString(str);
    int[] pArr = new int[charArr.length];
     int index = -1;
    int pR = -1;
    int maxContainsEnd = -1;
    for (int i = 0; i !=charArr.length; i++){
        pArr[i] = pR > i ? Math.min(pArr[2 * index - 1], pR - i) : 1;
        while( i + pArr[i] < charArr.length && i - pArr[i] > -1){
            if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
                pArr[i]++;
            else{
                break;
            }
        }
        if (i + pArr[i] > pR){
            pR = i + pArr[i];
            index = i;
        }
        if (pR == charArr.length){
            maxContainsEnd = pArr[i];
            break;
        }
    }
    char[] res = new char[str.length() - maxContainsEnd + 1];
    for (int i = 0 ;i< res.length; i++){
        res[res.length - 1 - i] = charArr[i * 2 + 1];
    }
    return String.valueOf(res);
}

BFPRT

1.分组(每组数量无所谓,bfprt是五个人,故每组5个)

2.组内排序

3.中位数拿出,组成N/5大小的新数组

4.num = bfprt(new_arr,new_arr.length/2)

5.用得到的num划分原数组

6.若num不是所需的k,则向左向右进行选择

时间复杂度:

O(1)、O(N)、O(N)、T(N/5)、O(N)、T(7 N/10 )

T(N) = T(N/5) + T(7 N/10) + O(N) —>O(N)

public int[] getMinKNumsByBFPRT(int[] arr, int k){
    if (k < 1 || k > arr.length){ return arr; }
    int minKth = getMinKthByBFPRT(arr, k);
    int[] res = new int[k];
    int index = 0;
    for (int i = 0; i != arr.length; i++){
        if (arr[i] < minKth){
            res[index++] = arr[i];
        }
    }
    for (; index != res.length; index++){
        res[index] = minKth;
    }
    return res;
}

public int getMinKthByBFPRT(int[] arr, int K){
    int[] copyArr = copyArray(arr);
    return select(copyArr, 0, copyArr.length - 1, K - 1);
}

public int[] copyArray(int[] arr){
    int[] res = new int[arr.length];
    for (int i = 0; i != res.length; i++){
        res[i] = arr[i];
    }
    return res;
}

public int select(int[] arr, int begin, int end, int i){
    if (begin == end){ return arr[begin]; }
    int pivot = medianOfMedians(arr, begin, end);
    int[] pivotRange = partition(arr, begin, end, pivot);
    if (i >= pivotRange[0] && i <= pivotRange[1]){
        return arr[i];
    } else if (i < pivotRange[0]){
        return select(arr, begin, pivotRange[0] - 1, i);
    } else {
        return select(arr, pivotRange[1] + 1, end, i);
    }
}

public int medianOfMedians(int[] arr, int begin, int end){
    int num = end - begin +1;
    int offset = num % 5 == 0 ? 0 : 1;
    int[] mArr = new int[num / 5 + offset];
    for (int i = 0; i < mArr.length; i++){
        int beginI = begin + i * 5;
        int endI = beginI + 4;
        mArr[i] = getMedian(arr, bgeinI, Math.min(end, endI));
    }
    return select(mArr, 0, mArr.length - 1, mArr.length / 2);
}

public int[] partition(int[] arr, int begin, int end, int pivotValue){
    int small = begin - 1;
    int cur = begin;
    int big = end + 1;
    while (cur != big){
        if (arr[cur] < pivotValue){
            swap(arr, ++small, cur++);
        } else if (arr[cur] > pivotValue){
            swap(arr, cur, --big);
        } else{
            curr++;
        }
    }
    int[] range = new int[2];
    range[0] = small + 1;
    range[1] = big -1;
    return range;
}

public int getMedian(int[] arr, inr begin, int end){
    insertionSort(arr, begin, end);
    int sum = end + begin;
    int mid = (sum / 2) + (sum % 2);
    return arr[mid];
}

public void insertionSort(int[] arr, int begin, int end){
    for (int i = begin + 1; i != end +1; i++){
        for (int j = i ;j != begin; j--){
            if (arr[j-1] > arr[j]) swap(arr, j - 1, j);
            else break;
        }
    }
}

用处:

找出无序数组最小的k个数

用堆的话,O(NlogN)

求窗口中的最大值数组

用双端队列,头部(大)—>尾部(小),L、R表示窗口边界。

R右移,判断要进入的数与队列尾部的数的大小,若尾部的数大,从尾部进入队列,维持头大尾小;若尾部的数小,队列从尾部依次出数,直到比要进入的数大为止;如果等于,队列先出,然后数进队列,保证进入时间的最新。

即,整个双端队列维持,头大尾小,且相同时留下最新的

public static int[] getMaxWindow(int[] arr, int w){
    if (arr == null || w < 1 || arr.length < w){ return null; }
    LinkedList<Integer> qmax = new LinkedList<Integer>();
    int[] res = new int[arr.length - w + 1];
    int index = 0;
    for (int i = 0; i < arr.length; i++){
        //更新双端队列
        while (!qmax.isEmpty() && arr[qmax.peakLast()] <= arr[i]){
            qmax.pollLast();
        }
        qmax.addLast(i);
        if (qmax.peekFirst() == i - w){
            qmax.pollFirst();
        }
        //判断下标过期
        if (i >= w - 1){
            res[index++] = arr[qmax.peekFirst()];
        }
    }
    return res;
}

最大值减最小值小于等于num的子数组个数

双端队列,L、R为数组边界,更新max、min,依次往右遍历

public static int getNum(int[] arr, int num){
    if (arr == null || arr.length == 0){ return 0; }
    LinkedList<Integer> qmax = new LinkedList<Integer>();
    LinkedList<Integer> qmin = new LinkedList<Integer>();
    int i = 0;
    int j = 0;
    int res = 0;
    while (i < arr.length){
        while (j < arr.length){
            while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j]){
                qmin.pollLast();
            }
            qmin.addLast(j);
            while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j]){
                qmax.pollLast();
            }
            qmax.addLast(j);
            if (arr[qmqx.getFirst()] - arr[qmin.getFirst()] >num){
                break;
            }
            j++;
        }
        if (qmin.peekFirst() == i){
            qmin.pollFirst;
        }
        if (qmax.peekFirst() == i){
            qmax.pollFirst();
        }
        res += j - i;
     	i++;
    }
    return res;
}

单调栈

得到数组每个值左右两侧最近比这个值大的信息,O(N)

建立一个底大顶小的栈,每次更新栈根据以下规则:

维持底大顶小的顺序

当新进的数比栈顶的数大时,(若没有新进数,则为null)栈顶数的右侧最近大的值为这个新进数,栈顶数的左侧最近大的值为栈中下一个(若没有下一个,则为null),弹出栈顶,比较新进的数与新栈顶。

若相等,下标位置的信息压栈,压在一起

构造数组的MaxTree

1.建立大根堆,时间复杂度O(N)

2.使用单调栈

求最大子矩阵的大小

时间复杂度O(N*M)

public int maxRecSize(int[][] map){
    if (map == null || map.length == 0 || map[0].length == 0){ return 0;}
    int maxArea = 0;
    int[] height = new int[map[0].length];
    for (int i = 0; i < map.length; i++){
        for (int j = 0; j < map[0].length; j++){
            height[j] = map[i][j] == 0 ? 0 : height[j] + 1;
        }
        maxArea = Math.max(maxRecFromBottom(height), maxArea);
    }
    return maxArea;
}

public int maxRecFromBottom(int[] height){
    if (height == null || height.length == 0){ return 0; }
    int maxArea = 0;
    Stack<Integer> stack = new Stack<Integer>();
    for (int i = 0; i < height.length; i++){
        while (!stack.isEmpty() && height[i] <= height[stack.peek()]){
            int j = stack.pop();
            int k = stack.isEmpty() ? - 1 : stack.peek();
            int curArea = (i - k - 1) *height[j];
            maxArea = Math.max(maxArea, curArea);
        }
        stack.push(i);
    }
    while (!stack.isEmpty()){
        int j = stack.pop();
        int k = stack.isEmpty() ? -1 : stack.peek();
        int curArea = (height.length - k - 1) * height[j];
        maxArea = Math.max(maxArea, curArea);
    }
    return maxArea;
}

回形山

public static int nextIndex(int size , int i){
    return i < (size - 1) ? (i + 1) :0;
}
public static long getInteralSum(int n){
    return n == 1L ? 0L : (long) n * (long) (n - 1) / 2L;
}
public static class Pair{
    public int value;
    public int times;
    
    public Pair(int value){
        this.value = value;
        this.times = 1;
    }
}

public static long communications(int[] arr){
    if (arr == null || arr.length < 2){ return 0; }
    int size = arr.length;
    int maxIndex = 0;
    for (int i = 0; i < size; i++){
        maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;
    }
    int value = arr[maxIndex];
    int index = nextIndex(size, maxIndex);
    long res = 0L;
    Stack<Pair> stack = new Stack<Pair>();
    stack.push(new Pair(value));
    while (index != maxIndex){
        value = arr[index];
        while(!stack.isEmpty() && stack.peek().value < value){
            int times = stack.pop().times;
           // res += getInernalSum(times) +times;
           // res += stack.isEmpty() ? 0 : times;
            res += getInternalSum(times) + 2 * times;
        }
        if (!stack.isEmpty() && stack.peek().value == value){
            stack.peek().times++;
        } else{
            stack.push(new Pair(value));
        }
        index = nextIndex(size, idnex);
    }
    while (!stack.isEmpty()){
        int times = stack.pop().times;
        res += getInternalSim(times);
        if (!stack.isEmpty()){
            res += times;
            if (stack.size() > 1){
                res += times;
            } else{
                res += stack.peek().times > 1 ? times : 0;
            }
        } 
    }
    return res;
}

Morris遍历

经典二叉树,由于没有指向父节点的指针,故遍历时都需要一个栈(递归:系统递归函数,非递归:自己写)来保存有关父节点的信息,都会造成O(H)的额外空间复杂度,H为二叉树高度。

//普通递归版
public static void process(Node head){
    if (head == null) { return; }
    //1
    //System.out.println(head.value)
    process(head.left);
    //2
    //System.out.println(head.value)
    process(head.right);
    //3
    //System.out.println(head.value)
}

普通遍历递归,可以三次来到当前节点,按打印时机可分为前中后序。

morris遍历,如果有左子树,可以来到当前节点两次,若没有左子树,来到当前节点一次,可以利用左子树最右节点右指针指向谁来标记第一次还是第二次到这个节点(左子树最右指针指向null,第一次到,指向当前节点,即指向自己,第二次到)。但是在遍历右子树时,无法第三次回到自己。

morris遍历第一次到时打印,先序。第二次到时打印(没有左子树的一次可以理解为直到第一次与第二次重叠在一起),中序

morris后序,只关注能来到两次的节点,在第二次来到这个节点的时候,逆序打印其左子树的右边界,整个函数退出之前单独打印整个树的右边界。逆序打印:利用链表的reverse修改指针指向,打印后,改回指针。

morris遍历的空间复杂度:O(1)

1.来到的当前节点记为cur,如果cur无左孩子,cur向右移动(cur = cur.right)

2.如果cur有左孩子,找到cur左子树上最右的节点,记为mostright

​ 1)若mostright的right指针指向空,让其指向cur,cur向左移动(cur = cur.left)

​ 2)若mostright的right指针指向cur,让其指向空,cur向右移动

public static void process(Node head) {
		if(head == null) {
			return;
		}
		
		// 1
		//System.out.println(head.value);
		
		
		process(head.left);
		
		// 2
		//System.out.println(head.value);
		
		
		process(head.right);
		
		// 3
		//System.out.println(head.value);
	}
	
	
	public static class Node {
		public int value;
		Node left;
		Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static void morrisIn(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
				}
			}
			System.out.print(cur1.value + " ");
			cur1 = cur1.right;
		}
		System.out.println();
	}

	public static void morrisPre(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					System.out.print(cur1.value + " ");
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
				}
			} else {
				System.out.print(cur1.value + " ");
			}
			cur1 = cur1.right;
		}
		System.out.println();
	}

	public static void morrisPos(Node head) {
		if (head == null) {
			return;
		}
		Node cur1 = head;
		Node cur2 = null;
		while (cur1 != null) {
			cur2 = cur1.left;
			if (cur2 != null) {
				while (cur2.right != null && cur2.right != cur1) {
					cur2 = cur2.right;
				}
				if (cur2.right == null) {
					cur2.right = cur1;
					cur1 = cur1.left;
					continue;
				} else {
					cur2.right = null;
					printEdge(cur1.left);
				}
			}
			cur1 = cur1.right;
		}
		printEdge(head);
		System.out.println();
	}

	public static void printEdge(Node head) {
		Node tail = reverseEdge(head);
		Node cur = tail;
		while (cur != null) {
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		reverseEdge(tail);
	}

	public static Node reverseEdge(Node from) {
		Node pre = null;
		Node next = null;
		while (from != null) {
			next = from.right;
			from.right = pre;
			pre = from;
			from = next;
		}
		return pre;
	}

BST、AVL、红黑树、SBT

大楼轮廓

给定一个N行3列二维数组,每一行表示有一座大楼,一共有N座大楼。
所有大楼的底部都坐落在X轴上,每一行的三个值(a,b,c)代表每座大楼的从(a,0)点开始,到
(b,0)点结束,高度为c。
输入的数据可以保证a 请输出整体的轮廓线。

public static class Node {
		public boolean be;
		public int p;
		public int h;

		public Node(boolean bORe, int position, int height) {
			be = bORe;
			p = position;
			h = height;
		}
	}

	public static class NodeComparator implements Comparator<Node> {
		@Override
		public int compare(Node o1, Node o2) {
			if (o1.p != o2.p) {
				return o1.p - o2.p;
			}
			if (o1.be != o2.be) {
				return o1.be ? -1 : 1;
			}
			return 0;
		}
	}

	public static List<List<Integer>> buildingOutline(int[][] buildings) {
		Node[] nodes = new Node[buildings.length * 2];
		for (int i = 0; i < buildings.length; i++) {
			nodes[i * 2] = new Node(true, buildings[i][0], buildings[i][2]);
			nodes[i * 2 + 1] = new Node(false, buildings[i][1], buildings[i][2]);
		}
		Arrays.sort(nodes, new NodeComparator());
		TreeMap<Integer, Integer> htMap = new TreeMap<>();
		TreeMap<Integer, Integer> pmMap = new TreeMap<>();
		for (int i = 0; i < nodes.length; i++) {
			if (nodes[i].be) {
				if (!htMap.containsKey(nodes[i].h)) {
					htMap.put(nodes[i].h, 1);
				} else {
					htMap.put(nodes[i].h, htMap.get(nodes[i].h) + 1);
				}
			} else {
				if (htMap.containsKey(nodes[i].h)) {
					if (htMap.get(nodes[i].h) == 1) {
						htMap.remove(nodes[i].h);
					} else {
						htMap.put(nodes[i].h, htMap.get(nodes[i].h) - 1);
					}
				}
			}
			if (htMap.isEmpty()) {
				pmMap.put(nodes[i].p, 0);
			} else {
				pmMap.put(nodes[i].p, htMap.lastKey());
			}
		}
		List<List<Integer>> res = new ArrayList<>();
		int start = 0;
		int height = 0;
		for (Entry<Integer, Integer> entry : pmMap.entrySet()) {
			int curPosition = entry.getKey();
			int curMaxHeight = entry.getValue();
			if (height != curMaxHeight) {
				if (height != 0) {
					List<Integer> newRecord = new ArrayList<Integer>();
					newRecord.add(start);
					newRecord.add(curPosition);
					newRecord.add(height);
					res.add(newRecord);
				}
				start = curPosition;
				height = curMaxHeight;
			}
		}
		return res;
	}

累加和为给定值的最长子数组

给定一个数组arr,和一个整数num,求在arr中,累加和等于num的最长子数组的长度

public static int maxLength(int[] arr, int k) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
		map.put(0, -1); // important
		int len = 0;
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
			if (map.containsKey(sum - k)) {
				len = Math.max(i - map.get(sum - k), len);
			}
			if (!map.containsKey(sum)) {
				map.put(sum, i);
			}
		}
		return len;
	}

三种变形:

一个数组中有A奇数有偶数、B只有0和1、C有0,1,2,求奇数偶数个数相等、0和1个数相等、1和2个数相等的最长子数组。

A奇数变为1,偶数为-1、B 0变为-1、C 0不变,2变为-1,求和为0的最长子数组。

异或和为0的最大子数组

定义数组的异或和的概念:
数组中所有的数异或起来,得到的结果叫做数组的异或和,比如数组{3,2,1}的异或和是,321 = 0
给定一个数组arr,你可以任意把arr分成很多不相容的子数组,你的目的是:
分出来的子数组中,异或和为0的子数组最多。
请返回:分出来的子数组中,异或和为0的子数组最多是多少

public static int mostEOR(int[] arr) {
		int ans = 0;
		int xor = 0;
		int[] mosts = new int[arr.length];
		HashMap<Integer, Integer> map = new HashMap<>();
		map.put(0, -1);
		for (int i = 0; i < arr.length; i++) {
			xor ^= arr[i];
			if (map.containsKey(xor)) {
				int pre = map.get(xor);
				mosts[i] = pre == -1 ? 1 : (mosts[pre] + 1);
			}
			if (i > 0) {
				mosts[i] = Math.max(mosts[i - 1], mosts[i]);
			}
			map.put(xor, i);
			ans = Math.max(ans, mosts[i]);
		}
		return ans;
	}

二叉树套路:

树形DP

黑盒拿到左树信息,右树信息,再与由当前节点的树相比

自己返回时拆黑盒,注意返回的信息怎么算

找到二叉树中最大搜索二叉子树

public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node biggestSubBST(Node head) {
		int[] record = new int[3]; // 0->size, 1->min, 2->max
		return posOrder(head, record);
	}
	
	public static class ReturnType{
		public int size;
		public Node head;
		public int min;
		public int max;
		
		public ReturnType(int a, Node b,int c,int d) {
			this.size =a;
			this.head = b;
			this.min = c;
			this.max = d;
		}
	}
	
	public static ReturnType process(Node head) {
		if(head == null) {
			return new ReturnType(0,null,Integer.MAX_VALUE, Integer.MIN_VALUE);
		}
		Node left = head.left;
		ReturnType leftSubTressInfo = process(left);
		Node right = head.right;
		ReturnType rightSubTressInfo = process(right);
		
		int includeItSelf = 0;
		if(leftSubTressInfo.head == left 
				&&rightSubTressInfo.head == right
				&& head.value > leftSubTressInfo.max
				&& head.value < rightSubTressInfo.min
				) {
			includeItSelf = leftSubTressInfo.size + 1 + rightSubTressInfo.size;
		}
		int p1 = leftSubTressInfo.size;
		int p2 = rightSubTressInfo.size;
		int maxSize = Math.max(Math.max(p1, p2), includeItSelf);
		
		Node maxHead = p1 > p2 ? leftSubTressInfo.head : rightSubTressInfo.head;
		if(maxSize == includeItSelf) {
			maxHead = head;
		}
		
		return new ReturnType(maxSize,
				maxHead, 
				Math.min(Math.min(leftSubTressInfo.min,rightSubTressInfo.min),head.value),
				Math.max(Math.max(leftSubTressInfo.max,rightSubTressInfo.max),head.value));	
	}

求一棵二叉树上的最远距离

二叉树中,一个节点可以往上走和往下走,那么从节点A总能走到节点B。

节点A走到节点B的距离为:A走到B最短路径上的节点个数。

public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static int maxDistance(Node head) {
		int[] record = new int[1];
		return posOrder(head, record);
	}
	
	public static class ReturnType{
		public int maxDistance;
		public int h;
		
		public ReturnType(int m, int h) {
			this.maxDistance = m;;
			this.h = h;
		}
	}
	
	public static ReturnType process(Node head) {
		if(head == null) {
			return new ReturnType(0,0);
		}
		ReturnType leftReturnType = process(head.left);
		ReturnType rightReturnType = process(head.right);
		int includeHeadDistance = leftReturnType.h + 1 + rightReturnType.h;
		int p1 = leftReturnType.maxDistance;
		int p2 = rightReturnType.maxDistance;
		int resultDistance = Math.max(Math.max(p1, p2), includeHeadDistance);
		int hitself  = Math.max(leftReturnType.h, leftReturnType.h) + 1;
		return new ReturnType(resultDistance, hitself);
	}

最大的活跃值

一个公司的上下节关系是一棵多叉树,这个公司要举办晚会,你作为组织者已经摸清了大家的心理:一个员工的直
接上级如果到场,这个员工肯定不会来。每个员工都有一个活跃度的值,决定谁来你会给这个员工发邀请函,怎么
让舞会的气氛最活跃?返回最大的活跃值。
举例:
给定一个矩阵来表述这种关系
matrix =
{
1,6
1,5
1,4
}
这个矩阵的含义是:
matrix[0] = {1 , 6},表示0这个员工的直接上级为1,0这个员工自己的活跃度为6
matrix[1] = {1 , 5},表示1这个员工的直接上级为1(他自己是这个公司的最大boss),1这个员工自己的活跃度
为5
matrix[2] = {1 , 4},表示2这个员工的直接上级为1,2这个员工自己的活跃度为4
为了让晚会活跃度最大,应该让1不来,0和2来。最后返回活跃度为10

每个节点:来、不来 两种情形

来的活跃度 = 所有子节点不来活跃度之和

不来的活跃度 = 子节点来、不来中较大的活跃度相加

public static int maxHappy(int[][] matrix) {
		int[][] dp = new int[matrix.length][2];
		boolean[] visited = new boolean[matrix.length];
		int root = 0;
		for (int i = 0; i < matrix.length; i++) {
			if (i == matrix[i][0]) {
				root = i;
			}
		}
		process(matrix, dp, visited, root);
		return Math.max(dp[root][0], dp[root][1]);
	}

	public static void process(int[][] matrix, int[][] dp, boolean[] visited, int root) {
		visited[root] = true;
		dp[root][1] = matrix[root][1];
		for (int i = 0; i < matrix.length; i++) {
			if (matrix[i][0] == root && !visited[i]) {
				process(matrix, dp, visited, i);
				dp[root][1] += dp[i][0];
				dp[root][0] += Math.max(dp[i][1], dp[i][0]);
			}
		}
	}

判断一棵树是否是平衡二叉树

public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static boolean isBalance(Node head) {
		boolean[] res = new boolean[1];
		res[0] = true;
		getHeight(head, 1, res);
		return res[0];
	}
	
	public static class ReturnType {
		public int level;
		public boolean isB;
		
		public ReturnType(int l, boolean is) {
			level = l;
			isB = is;
		}
	}
	
	// process(head, 1)
	
	public static ReturnType process(Node head, int level) {
		if (head == null) {
			return new ReturnType(level, true);
		}
		ReturnType leftSubTreeInfo = process(head.left, level + 1);
		if(!leftSubTreeInfo.isB) {
			return new ReturnType(level, false);
		}
		ReturnType rightSubTreeInfo = process(head.right, level + 1);
		if(!rightSubTreeInfo.isB) {
			return new ReturnType(level, false);
		}
		if (Math.abs(rightSubTreeInfo.level - leftSubTreeInfo.level) > 1) {
			return new ReturnType(level, false);
		}
		return new ReturnType(Math.max(leftSubTreeInfo.level, rightSubTreeInfo.level), true);
	}

	public static int getHeight(Node head, int level, boolean[] res) {
		if (head == null) {
			return level;
		}
		int lH = getHeight(head.left, level + 1, res);
		if (!res[0]) {
			return level;
		}
		int rH = getHeight(head.right, level + 1, res);
		if (!res[0]) {
			return level;
		}
		if (Math.abs(lH - rH) > 1) {
			res[0] = false;
		}
		return Math.max(lH, rH);
	}

设计可以变更的缓存结构(LRU)

【题目】
设计一种缓存结构,该结构在构造时确定大小,假设大小为K,并有两个功能:
set(key,value):将记录(key,value)插入该结构。
get(key):返回key对应的value值。
【要求】
1.set和get方法的时间复杂度为O(1)。
2.某个key的set或get操作一旦发生,认为这个key的记录成了最经常使用的。
3.当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

public static class Node<V> {
		public V value;
		public Node<V> last;
		public Node<V> next;

		public Node(V value) {
			this.value = value;
		}
	}

	public static class NodeDoubleLinkedList<V> {
		private Node<V> head;
		private Node<V> tail;

		public NodeDoubleLinkedList() {
			this.head = null;
			this.tail = null;
		}

		public void addNode(Node<V> newNode) {
			if (newNode == null) {
				return;
			}
			if (this.head == null) {
				this.head = newNode;
				this.tail = newNode;
			} else {
				this.tail.next = newNode;
				newNode.last = this.tail;
				this.tail = newNode;
			}
		}

		public void moveNodeToTail(Node<V> node) {
			if (this.tail == node) {
				return;
			}
			if (this.head == node) {
				this.head = node.next;
				this.head.last = null;
			} else {
				node.last.next = node.next;
				node.next.last = node.last;
			}
			node.last = this.tail;
			node.next = null;
			this.tail.next = node;
			this.tail = node;
		}

		public Node<V> removeHead() {
			if (this.head == null) {
				return null;
			}
			Node<V> res = this.head;
			if (this.head == this.tail) {
				this.head = null;
				this.tail = null;
			} else {
				this.head = res.next;
				res.next = null;
				this.head.last = null;
			}
			return res;
		}

	}

	public static class MyCache<K, V> {
		private HashMap<K, Node<V>> keyNodeMap;
		private HashMap<Node<V>, K> nodeKeyMap;
		private NodeDoubleLinkedList<V> nodeList;
		private int capacity;

		public MyCache(int capacity) {
			if (capacity < 1) {
				throw new RuntimeException("should be more than 0.");
			}
			this.keyNodeMap = new HashMap<K, Node<V>>();
			this.nodeKeyMap = new HashMap<Node<V>, K>();
			this.nodeList = new NodeDoubleLinkedList<V>();
			this.capacity = capacity;
		}
public V get(K key) {
			if (this.keyNodeMap.containsKey(key)) {
				Node<V> res = this.keyNodeMap.get(key);
				this.nodeList.moveNodeToTail(res);
				return res.value;
			}
			return null;
		}

		public void set(K key, V value) {
			if (this.keyNodeMap.containsKey(key)) {
				Node<V> node = this.keyNodeMap.get(key);
				node.value = value;
				this.nodeList.moveNodeToTail(node);
			} else {
				Node<V> newNode = new Node<V>(value);
				this.keyNodeMap.put(key, newNode);
				this.nodeKeyMap.put(newNode, key);
				this.nodeList.addNode(newNode);
				if (this.keyNodeMap.size() == this.capacity + 1) {
					this.removeMostUnusedCache();
				}
			}
		}

		private void removeMostUnusedCache() {
			Node<V> removeNode = this.nodeList.removeHead();
			K removeKey = this.nodeKeyMap.get(removeNode);
			this.nodeKeyMap.remove(removeNode);
			this.keyNodeMap.remove(removeKey);
		}

LFU

public static class Node {
		public Integer key;
		public Integer value;
		public Integer times;
		public Node up;
		public Node down;

		public Node(int key, int value, int times) {
			this.key = key;
			this.value = value;
			this.times = times;
		}
	}

	public static class LFUCache {

		public static class NodeList {
			public Node head;
			public Node tail;
			public NodeList last;
			public NodeList next;

			public NodeList(Node node) {
				head = node;
				tail = node;
			}

			public void addNodeFromHead(Node newHead) {
				newHead.down = head;
				head.up = newHead;
				head = newHead;
			}

			public boolean isEmpty() {
				return head == null;
			}

			public void deleteNode(Node node) {
				if (head == tail) {
					head = null;
					tail = null;
				} else {
					if (node == head) {
						head = node.down;
						head.up = null;
					} else if (node == tail) {
						tail = node.up;
						tail.down = null;
					} else {
						node.up.down = node.down;
						node.down.up = node.up;
					}
				}
				node.up = null;
				node.down = null;
			}
		}

		private int capacity;
		private int size;
		private HashMap<Integer, Node> records;
		private HashMap<Node, NodeList> heads;
		private NodeList headList;

		public LFUCache(int capacity) {
			this.capacity = capacity;
			this.size = 0;
			this.records = new HashMap<>();
			this.heads = new HashMap<>();
			headList = null;
		}

		public void set(int key, int value) {
			if (records.containsKey(key)) {
				Node node = records.get(key);
				node.value = value;
				node.times++;
				NodeList curNodeList = heads.get(node);
				move(node, curNodeList);
			} else {
				if (size == capacity) {
					Node node = headList.tail;
					headList.deleteNode(node);
					modifyHeadList(headList);
					records.remove(node.key);
					heads.remove(node);
					size--;
				}
				Node node = new Node(key, value, 1);
				if (headList == null) {
					headList = new NodeList(node);
				} else {
					if (headList.head.times.equals(node.times)) {
						headList.addNodeFromHead(node);
					} else {
						NodeList newList = new NodeList(node);
						newList.next = headList;
						headList.last = newList;
						headList = newList;
					}
				}
				records.put(key, node);
				heads.put(node, headList);
				size++;
			}
		}

		private void move(Node node, NodeList oldNodeList) {
			oldNodeList.deleteNode(node);
			NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last
					: oldNodeList;
			NodeList nextList = oldNodeList.next;
			if (nextList == null) {
				NodeList newList = new NodeList(node);
				if (preList != null) {
					preList.next = newList;
				}
				newList.last = preList;
				if (headList == null) {
					headList = newList;
				}
				heads.put(node, newList);
			} else {
				if (nextList.head.times.equals(node.times)) {
					nextList.addNodeFromHead(node);
					heads.put(node, nextList);
				} else {
					NodeList newList = new NodeList(node);
					if (preList != null) {
						preList.next = newList;
					}
					newList.last = preList;
					newList.next = nextList;
					nextList.last = newList;
					if (headList == nextList) {
						headList = newList;
					}
					heads.put(node, newList);
				}
			}
		}

		// return whether delete this head
		private boolean modifyHeadList(NodeList nodeList) {
			if (nodeList.isEmpty()) {
				if (headList == nodeList) {
					headList = nodeList.next;
					if (headList != null) {
						headList.last = null;
					}
				} else {
					nodeList.last.next = nodeList.next;
					if (nodeList.next != null) {
						nodeList.next.last = nodeList.last;
					}
				}
				return true;
			}
			return false;
		}

		public int get(int key) {
			if (!records.containsKey(key)) {
				return -1;
			}
			Node node = records.get(key);
			node.times++;
			NodeList curNodeList = heads.get(node);
			move(node, curNodeList);
			return node.value;
		}

	}

公式的计算结果

给定一个字符串str,str表示一个公式,公式里可能有整数、加减乘除符号和
左右括号,返回公式的计算结果。

【举例】
str=“48*((70-65)-43)+81",返回-1816。
str="3+1
4”,返回7。 str=“3+(14)",返回7。
【说明】
1.可以认为给定的字符串一定是正确的公式,即不需要对str做公式有效性检
查。
2.如果是负数,就需要用括号括起来,比如"4
(-3)”。但如果负数作为公式的
开头或括号部分的开头,则可以没有括号,比如"-34"和"(-34)"都是合法的。
3.不用考虑计算过程中会发生溢出的情况

public static int getValue(String str) {
		return value(str.toCharArray(), 0)[0];
	}

	public static int[] value(char[] str, int i) {
		LinkedList<String> que = new LinkedList<String>();
		int pre = 0;
		int[] bra = null;
		while (i < str.length && str[i] != ')') {
			if (str[i] >= '0' && str[i] <= '9') {
				pre = pre * 10 + str[i++] - '0';
			} else if (str[i] != '(') {
				addNum(que, pre);
				que.addLast(String.valueOf(str[i++]));
				pre = 0;
			} else {
				bra = value(str, i + 1);
				pre = bra[0];
				i = bra[1] + 1;
			}
		}
		addNum(que, pre);
		return new int[] { getNum(que), i };
	}

	public static void addNum(LinkedList<String> que, int num) {
		if (!que.isEmpty()) {
			int cur = 0;
			String top = que.pollLast();
			if (top.equals("+") || top.equals("-")) {
				que.addLast(top);
			} else {
				cur = Integer.valueOf(que.pollLast());
				num = top.equals("*") ? (cur * num) : (cur / num);
			}
		}
		que.addLast(String.valueOf(num));
	}

	public static int getNum(LinkedList<String> que) {
		int res = 0;
		boolean add = true;
		String cur = null;
		int num = 0;
		while (!que.isEmpty()) {
			cur = que.pollFirst();
			if (cur.equals("+")) {
				add = true;
			} else if (cur.equals("-")) {
				add = false;
			} else {
				num = Integer.valueOf(cur);
				res += add ? num : (-num);
			}
		}
		return res;
	}

跳表

public static class SkipListNode {
		public Integer value;
		public ArrayList<SkipListNode> nextNodes;

		public SkipListNode(Integer value) {
			this.value = value;
			nextNodes = new ArrayList<SkipListNode>();
		}
	}

	public static class SkipListIterator implements Iterator<Integer> {
		SkipList list;
		SkipListNode current;

		public SkipListIterator(SkipList list) {
			this.list = list;
			this.current = list.getHead();
		}

		public boolean hasNext() {
			return current.nextNodes.get(0) != null;
		}

		public Integer next() {
			current = current.nextNodes.get(0);
			return current.value;
		}
	}

	public static class SkipList {
		private SkipListNode head;
		private int maxLevel;
		private int size;
		private static final double PROBABILITY = 0.5;

		public SkipList() {
			size = 0;
			maxLevel = 0;
			head = new SkipListNode(null);
			head.nextNodes.add(null);
		}

		public SkipListNode getHead() {
			return head;
		}

		public void add(Integer newValue) {
			if (!contains(newValue)) {
				size++;
				int level = 0;
				while (Math.random() < PROBABILITY) {
					level++;
				}
				while (level > maxLevel) {
					head.nextNodes.add(null);
					maxLevel++;
				}
				SkipListNode newNode = new SkipListNode(newValue);
				SkipListNode current = head;
				do {
					current = findNext(newValue, current, level);
					newNode.nextNodes.add(0, current.nextNodes.get(level));
					current.nextNodes.set(level, newNode);
				} while (level-- > 0);
			}
		}

		public void delete(Integer deleteValue) {
			if (contains(deleteValue)) {
				SkipListNode deleteNode = find(deleteValue);
				size--;
				int level = maxLevel;
				SkipListNode current = head;
				do {
					current = findNext(deleteNode.value, current, level);
					if (deleteNode.nextNodes.size() > level) {
						current.nextNodes.set(level, deleteNode.nextNodes.get(level));
					}
				} while (level-- > 0);
			}
		}

		// Returns the skiplist node with greatest value <= e
		private SkipListNode find(Integer e) {
			return find(e, head, maxLevel);
		}

		// Returns the skiplist node with greatest value <= e
		// Starts at node start and level
		private SkipListNode find(Integer e, SkipListNode current, int level) {
			do {
				current = findNext(e, current, level);
			} while (level-- > 0);
			return current;
		}

		// Returns the node at a given level with highest value less than e
		private SkipListNode findNext(Integer e, SkipListNode current, int level) {
			SkipListNode next = current.nextNodes.get(level);
			while (next != null) {
				Integer value = next.value;
				if (lessThan(e, value)) { // e < value
					break;
				}
				current = next;
				next = current.nextNodes.get(level);
			}
			return current;
		}

		public int size() {
			return size;
		}

		public boolean contains(Integer value) {
			SkipListNode node = find(value);
			return node != null && node.value != null && equalTo(node.value, value);
		}

		public Iterator<Integer> iterator() {
			return new SkipListIterator(this);
		}

		/******************************************************************************
		 * Utility Functions *
		 ******************************************************************************/

		private boolean lessThan(Integer a, Integer b) {
			return a.compareTo(b) < 0;
		}

		private boolean equalTo(Integer a, Integer b) {
			return a.compareTo(b) == 0;
		}

	}

最大异或和

给定一个数组,求子数组的最大异或和。
一个数组的异或和为,数组中所有的数异或起来的结果。

dp、前缀树

用黑盒保存从0到i前所有可能的异或和最大值

除符号位之外,尽量满足高位变为1

public static class Node {
		public Node[] nexts = new Node[2];
	}

	public static class NumTrie {
		public Node head = new Node();

		public void add(int num) {
			Node cur = head;
			for (int move = 31; move >= 0; move--) {
                //从高位到地位,提取出数字
				int path = ((num >> move) & 1);
				cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path];
				cur = cur.nexts[path];
			}
		}
        
//num,从0到i的异或结果
//选出最优返回
		public int maxXor(int num) {
			Node cur = head;
			int res = 0;
			for (int move = 31; move >= 0; move--) {
				int path = (num >> move) & 1;
                
                //期待选的路:符号位的最好选择best是与之一样,数值位的是与之相同
				int best = move == 31 ? path : (path ^ 1);
                //实际选的路:有best选best路,没有的话,被迫走另一条路
				best = cur.nexts[best] != null ? best : (best ^ 1);
                //设置答案的每一位
				res |= (path ^ best) << move;
				cur = cur.nexts[best];
			}
			return res;
		}

	}

	public static int maxXorSubarray(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int max = Integer.MIN_VALUE;
		int eor = 0;
		NumTrie numTrie = new NumTrie();
		numTrie.add(0);
		for (int i = 0; i < arr.length; i++) {
			eor ^= arr[i];
			max = Math.max(max, numTrie.maxXor(eor));
			numTrie.add(eor);
		}
		return max;
	}

暴力递归改动态规划

换钱的方法数

【题目】
给定数组arr,arr中所有的值都为正数且不重复。每个值代表
一种面值的货币,每种面值的货币可以使用任意张,再给定一
个整数aim代表要找的钱数,求换钱有多少种方法。
【举例】
arr=[5,10,25,1],aim=0。
组成0元的方法有1种,就是所有面值的货币都不用。所以返回1。
arr=[5,10,25,1],aim=15。
组成15元的方法有6种,分别为3张5元、1张10元+1张5元、1张
10元+5张1元、10张1元+1张5元、2张5元+5张1元和15张1元。所
以返回6。
arr=[3,5],aim=2。
任何方法都无法组成2元。所以返回0。

首先,写出暴力递归版本,可以理解成dfs

public static int coins1(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		return process1(arr, 0, aim);
	}


//arr:不变的数组
//index:可以任意自由使用index及其之后的所有钱
//aim:目标钱数
//返回值:方法数
	public static int process1(int[] arr, int index, int aim) {
		int res = 0;
		if (index == arr.length) {
			res = aim == 0 ? 1 : 0;
		} else {
			for (int i = 0; arr[index] * i <= aim; i++) {
				res += process1(arr, index + 1, aim - arr[index] * i);
			}
		}
		return res;
	}

这个暴力递归的方法问题在于,有大量重复状态但未储存,每次遇到都得重新计算。

比如,使用2 * 200、4 * 100 或者2 * 100 + 200,即aim = 600 时,此时后面方法数与到这个状态的路径无关,无需重复计算。

改进1,建立一个缓存表储存重复状态即可。

找出哪些参数一旦固定,返回值就固定。

e.g. index与aim一旦固定,返回值就固定

无后效性问题

public static int coins2(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int[][] map = new int[arr.length + 1][aim + 1];
		return process2(arr, 0, aim, map);
	}

	public static int process2(int[] arr, int index, int aim, int[][] map) {
		int res = 0;
		if (index == arr.length) {
			res = aim == 0 ? 1 : 0;
		} else {
			int mapValue = 0;
			for (int i = 0; arr[index] * i <= aim; i++) {
				mapValue = map[index + 1][aim - arr[index] * i];
				if (mapValue != 0) {
					res += mapValue == -1 ? 0 : mapValue;
				} else {
					res += process2(arr, index + 1, aim - arr[index] * i, map);
				}
			}
		}
		map[index][aim] = res == 0 ? -1 : res;
		return res;
	}

该方法为记忆化搜索

改进2

参数的变化,可以囊括返回值的变化,参数2维,建立一张2维表(index从0到n,aim从0到aimall),这张表装下了所有返回值

分析可变参数的变化范围得到的2维表

定出目标所在位置

看这张表中哪些位置不依赖其他位置即可确定,即看递归中的base case

这里,根据

if (index == arr.length) {res = aim == 0 ? 1 : 0;}

index = n的时候,值是可以确定的,aim = 0时,表中位置为1,其他为0

然后,看位置依赖

表中(index,aim)位置根据

res += process1(arr, index + 1, aim - arr[index] * i);

arr[index]这里假设为5

找到(index + 1, aim)、(index + 1, aim - 5)。。。这些位置,直到越界,此时该表与题意依然无关,根据转态转移方程填表即可

public static int coins3(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int[][] dp = new int[arr.length][aim + 1];
		for (int i = 0; i < arr.length; i++) {
			dp[i][0] = 1;
		}
		for (int j = 1; arr[0] * j <= aim; j++) {
			dp[0][arr[0] * j] = 1;
		}
		int num = 0;
		for (int i = 1; i < arr.length; i++) {
			for (int j = 1; j <= aim; j++) {
				num = 0;
				for (int k = 0; j - arr[i] * k >= 0; k++) {
					num += dp[i - 1][j - arr[i] * k];
				}
				dp[i][j] = num;
			}
		}
		return dp[arr.length - 1][aim];
	}

改进3

建立dp表后,发现状态转移方程中,有些信息也可以不必每次计算

e.g. 若arr[range]为3

dp [index] [aim] = dp [index + 1] [aim] + dp [index + 1] [aim - 3] + dp [index + 1] [aim - 6] + …

而dp [index + 1] [aim - 3] = dp [index + 1] [aim - 3] + dp [index + 1] [aim - 6] + …,

即dp [index] [aim] = dp [index + 1] [aim] +dp [index + 1] [aim - 3]

空间优化

public static int coins4(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int[][] dp = new int[arr.length][aim + 1];
		for (int i = 0; i < arr.length; i++) {
			dp[i][0] = 1;
		}
		for (int j = 1; arr[0] * j <= aim; j++) {
			dp[0][arr[0] * j] = 1;
		}
		for (int i = 1; i < arr.length; i++) {
			for (int j = 1; j <= aim; j++) {
				dp[i][j] = dp[i - 1][j];
				dp[i][j] += j - arr[i] >= 0 ? dp[i][j - arr[i]] : 0;
			}
		}
		return dp[arr.length - 1][aim];
	}


排成一条线的纸牌博弈问题

【题目】
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走
每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右
的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】
arr=[1,2,100,4]。
开始时玩家A只能拿走1或4。如果玩家A拿走1,则排列变为[2,100,4],接下来玩
家B可以拿走2或4,然后继续轮到玩家A。如果开始时玩家A拿走4,则排列变为
[1,2,100],接下来玩家B可以拿走1或100,然后继续轮到玩家A。玩家A作为绝顶
聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1,让排
列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家A拿走。玩家A会获胜,
分数为101。所以返回101。
arr=[1,100,2]。
开始时玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会
获胜,分数为100。所以返回100。

public static int win1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
	}

	public static int f(int[] arr, int i, int j) {
		if (i == j) {
			return arr[i];
		}
		return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
	}

	public static int s(int[] arr, int i, int j) {
		if (i == j) {
			return 0;
		}
		return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
	}

	public static int win2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int[][] f = new int[arr.length][arr.length];
		int[][] s = new int[arr.length][arr.length];
		for (int j = 0; j < arr.length; j++) {
			f[j][j] = arr[j];
			for (int i = j - 1; i >= 0; i--) {
				f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
				s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
			}
		}
		return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
	}

累加和为给定值的最长子数组系列

三连:

1.可正可负可0,等于aim

见 四

2.正数,等于aim

3.可正可负可0,小于等于aim

给定一个数组arr,全是正数;一个整数aim,求累加和等
于aim的,最长子数组,要求额外空间复杂度O(1),时间
复杂度O(N)

public static int getMaxLength(int[] arr, int k) {
		if (arr == null || arr.length == 0 || k <= 0) {
			return 0;
		}
		int L = 0;
		int R = 0;
		int sum = arr[0];
		int len = 0;
		while (R < arr.length) {
			if (sum == k) {
				len = Math.max(len, R - L + 1);
				sum -= arr[L++];
			} else if (sum < k) {
				R++;
				if (R == arr.length) {
					break;
				}
				sum += arr[R];
			} else {
				sum -= arr[L++];
			}
		}
		return len;
	}

给定一个数组arr,值可正,可负,可0;一个整数aim,求累加
和小于等于aim的,最长子数组,要求时间复杂度O(N)

倒着生成两个信息(min_sum:以当前位置开始的最小累加和,min_sum_index:上述最小累加和的右边界),倒着生成的原因是可以保存位置较后的数字最小累加信息,加速

//方法1,O(N)
public static int maxLengthAwesome(int[] arr, int k) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int[] sums = new int[arr.length];
		HashMap<Integer, Integer> ends = new HashMap<Integer, Integer>();
		sums[arr.length - 1] = arr[arr.length - 1];
		ends.put(arr.length - 1, arr.length - 1);
		for (int i = arr.length - 2; i >= 0; i--) {
			if (sums[i + 1] < 0) {
				sums[i] = arr[i] + sums[i + 1];
				ends.put(i, ends.get(i + 1));
			} else {
				sums[i] = arr[i];
				ends.put(i, i);
			}
		}
		int end = 0;
		int sum = 0;
		int res = 0;
		for (int i = 0; i < arr.length; i++) {
			while (end < arr.length && sum + sums[end] <= k) {
				sum += sums[end];
				end = ends.get(end) + 1;
			}
			sum -= end > i ? arr[i] : 0;
			res = Math.max(res, end - i);
			end = Math.max(end, i + 1);
		}
		return res;
	}


//方法2:二分加速,O(logN)
	public static int maxLength(int[] arr, int k) {
		int[] h = new int[arr.length + 1];
		int sum = 0;
		h[0] = sum;
		for (int i = 0; i != arr.length; i++) {
			sum += arr[i];
			h[i + 1] = Math.max(sum, h[i]);
		}
		sum = 0;
		int res = 0;
		int pre = 0;
		int len = 0;
		for (int i = 0; i != arr.length; i++) {
			sum += arr[i];
			pre = getLessIndex(h, sum - k);
			len = pre == -1 ? 0 : i - pre + 1;
			res = Math.max(res, len);
		}
		return res;
	}

	public static int getLessIndex(int[] arr, int num) {
		int low = 0;
		int high = arr.length - 1;
		int mid = 0;
		int res = -1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (arr[mid] >= num) {
				res = mid;
				high = mid - 1;
			} else {
				low = mid + 1;
			}
		}
		return res;
	}

环形单链表的约瑟夫问题

【题目】
据说著名犹太历史学家Josephus有过以下故事:在罗马人占领乔塔帕特后,
39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也
不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个
人开始报数,报数到3的人就自杀,然后再由下一个人重新报1,报数到3的人
再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的
命运。这就是著名的约瑟夫问题。现在请用单向环形链表描述该结构并呈现整
个自杀过程。
输入:一个环形单向链表的头节点head和报数的值m。
返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都
删掉。
进阶:
如果链表节点数为N,想在时间复杂度为O(N)时完成原问题的要求,该怎么实
现?

public static Node josephusKill1(Node head, int m) {
		if (head == null || head.next == head || m < 1) {
			return head;
		}
		Node last = head;
		while (last.next != head) {
			last = last.next;
		}
		int count = 0;
		while (head != last) {
			if (++count == m) {
				last.next = head.next;
				count = 0;
			} else {
				last = last.next;
			}
			head = last.next;
		}
		return head;
	}

	public static Node josephusKill2(Node head, int m) {
		if (head == null || head.next == head || m < 1) {
			return head;
		}
		Node cur = head.next;
		int tmp = 1; // tmp -> list size
		while (cur != head) {
			tmp++;
			cur = cur.next;
		}
		tmp = getLive(tmp, m); // tmp -> service node position
		while (--tmp != 0) {
			head = head.next;
		}
		head.next = head;
		return head;
	}

	public static int getLive(int i, int m) {
		if (i == 1) {
			return 1;
		}
		return (getLive(i - 1, m) + m - 1) % i + 1;
    }
	

字符串匹配问题

【题目】
给定字符串str,其中绝对不含有字符’.‘和’’。再给定字符串exp,
其中可以含有’.‘或’
’,’'字符不能是exp的首字符,并且任意两个
'
‘字符不相邻。exp中的’.‘代表任何一个字符,exp中的’‘表示’
的前一个字符可以有0个或者多个。请写一个函数,判断str是否能被
exp匹配。
【举例】
str=“abc”,exp=“abc”,返回true。
str=“abc”,exp=“a.c”,exp中单个’.‘可以代表任意字符,所以返回
true。
str=“abcd”,exp="."。exp中’‘的前一个字符是’.’,所以可表示任
意数量的’.‘字符,当exp是"…“时与"abcd"匹配,返回true。
str=”",exp="…"。exp中’‘的前一个字符是’.’,可表示任意数量
的’.'字符,但是"."之前还有一个’.‘字符,该字符不受’'的影响,
所以str起码有一个字符才能被exp匹配。所以返回false。

public static boolean isValid(char[] s, char[] e) {
		for (int i = 0; i < s.length; i++) {
			if (s[i] == '*' || s[i] == '.') {
				return false;
			}
		}
		for (int i = 0; i < e.length; i++) {
			if (e[i] == '*' && (i == 0 || e[i - 1] == '*')) {
				return false;
			}
		}
		return true;
	}

	public static boolean isMatch(String str, String exp) {
		if (str == null || exp == null) {
			return false;
		}
		char[] s = str.toCharArray();
		char[] e = exp.toCharArray();
		return isValid(s, e) ? process(s, e, 0, 0) : false;
	}

	public static boolean process(char[] s, char[] e, int si, int ei) {
		if (ei == e.length) {
			return si == s.length;
		}
		if (ei + 1 == e.length || e[ei + 1] != '*') {
			return si != s.length && (e[ei] == s[si] || e[ei] == '.')
					&& process(s, e, si + 1, ei + 1);
		}
		while (si != s.length && (e[ei] == s[si] || e[ei] == '.')) {
			if (process(s, e, si, ei + 2)) {
				return true;
			}
			si++;
		}
		return process(s, e, si, ei + 2);
	}

	public static boolean isMatchDP(String str, String exp) {
		if (str == null || exp == null) {
			return false;
		}
		char[] s = str.toCharArray();
		char[] e = exp.toCharArray();
		if (!isValid(s, e)) {
			return false;
		}
		boolean[][] dp = initDPMap(s, e);
		for (int i = s.length - 1; i > -1; i--) {
			for (int j = e.length - 2; j > -1; j--) {
				if (e[j + 1] != '*') {
					dp[i][j] = (s[i] == e[j] || e[j] == '.')
							&& dp[i + 1][j + 1];
				} else {
					int si = i;
					while (si != s.length && (s[si] == e[j] || e[j] == '.')) {
						if (dp[si][j + 2]) {
							dp[i][j] = true;
							break;
						}
						si++;
					}
					if (dp[i][j] != true) {
						dp[i][j] = dp[si][j + 2];
					}
				}
			}
		}
		return dp[0][0];
	}

	public static boolean[][] initDPMap(char[] s, char[] e) {
		int slen = s.length;
		int elen = e.length;
		boolean[][] dp = new boolean[slen + 1][elen + 1];
		dp[slen][elen] = true;
		for (int j = elen - 2; j > -1; j = j - 2) {
			if (e[j] != '*' && e[j + 1] == '*') {
				dp[slen][j] = true;
			} else {
				break;
			}
		}
		if (slen > 0 && elen > 0) {
			if ((e[elen - 1] == '.' || s[slen - 1] == e[elen - 1])) {
				dp[slen - 1][elen - 1] = true;
			}
		}
		return dp;
	}

你可能感兴趣的:(算法刷题)