左神算法课进阶版总结

文章目录

      • 1、KMP算法
      • 2、递归的套路
      • 3、[马拉车算法](https://blog.csdn.net/tangyuan_sibal/article/details/90082784),
      • 4、BFPRT算法
      • 5、滑动窗口最大值
      • 6、单调栈
      • 7、morris遍历
      • 8、判断一棵树是否是平衡二叉树
      • 9、给定一个字符串代表一个表达式,其中包含括号,求其结果值
      • 10、一个数组中包含正、负、0的数,求其最长累加和为aim的子数组
      • 11、判断一棵树是否是完全二叉树

1、KMP算法

package niuke.zuoshen;

public class KMPAlgorithm {

	/**
	 * 根据next移动,找到匹配串的开端下标
	 * @param s1
	 * @param s2
	 * @return
	 */
	public static int getIndexOf(String s1, String s2)
	{
		char[] str1 = s1.toCharArray();
		char[] str2 = s2.toCharArray();
		
		int[] next = getNextArray(str2);
		
		int i1 = 0;//主子串下标
		int i2 = 0;//匹配子串下标
		while(i1 < str1.length && i2 < str2.length)
		{
			if(str1[i1] == str2[i2])//如果字符相等,则继续往前匹配
			{
				i1++;
				i2++;
			}
			else if(next[i2] == -1)//如果不相等,且当前字符已经没有最长前缀和最长后缀的匹配
			{
				i1++;//顺序匹配下一个字符
			}
			else {
				i2 = next[i2];//存在最长前缀和最长后缀相等大于0的,则从最长前缀的下一个字符开始匹配,最长前缀那一部分不用匹配是因为
							//一定是相等的
			}
		}
		return i2 == str2.length? i1 - i2:-1;//如果跳出循环的是因为匹配到最后一个字符正确跳出的,则存在子串,否则不存在返回-1
	}
	/**
	 * 计算next数组
	 * @param str2
	 * @return
	 */
	private static int[] getNextArray(char[] str2) {
		
		int[] next = new int[str2.length];
		next[0] = -1;
		next[1] = 0;//前面这两个数是认为规定的
		int i = 2;
		int cn = 0;//代表分界指标,这边有一个隐含的条件,最长前缀和最长后缀的长度==最长前缀的最后一个字符下标
		while(i < str2.length)
		{
			if(str2[i - 1] == str2[cn])
			{
				next[i++] = ++cn;
			}
			else if(cn > 0)//前一个字符和最长前缀的下一个字符不相等,则以最长前缀下一个字符为切分点继续切分
			{
				cn = next[cn];
			}
			else {
				next[i++] = 0;//cn来到了没有最长前缀,则i这个位置的next值为0
			}
		}
		return next;
	}
	public static void main(String[] args) {
		String s1 = "BBCABCDABABCDABCDABCDABDE";
		String s2 = "ABCDABD";
		int indexOf = getIndexOf(s1, s2);
		System.out.println(indexOf);
	}
}

2、递归的套路

重点在于两点,一个是是否有无后效性,另一个是如何写出递归的版本,然后将递归改成动态规划,而动态规划数组的元数就是递归的变量数。

package niuke.zuoshen;

/**
 * 判断给定一个全为正数数组中是否存在和为给定数
 * @author tangyuan
 *
 * 2019年7月1日
 * 

CopyRight:Copyright(c)2019

*/
public class IsEqualsDP { //{3,1,4,2,7} public static boolean isSum(int[] nums, int i, int sum, int target){//i一直往前走,不管选不选 if(i == nums.length-1)//当i走到了最后位置 return sum == target;//判断sum的值 return isSum(nums, i+1, sum, target)||isSum(nums, i+1, sum+nums[i], target);//这个方法会遍历所有的子集合 } public static boolean isSumByDP(int target,int[] arrs){ int sum = 0; for(int i = 0; i < arrs.length; i++) { sum += arrs[i]; } if(target > sum)return false;//所有值加起来都没有目标值大,直接返回false boolean[][] dp = new boolean[arrs.length+1][sum+1]; for(int j = 0; j <= sum; j++) {//先将最后一行已经知道结果的填充进去 dp[arrs.length][j] = j==target; } for(int i = arrs.length - 1; i >= 0; i--) for(int j = 0; j <= sum; j++) { if(j + arrs[i] <= sum){//不超出部分,j+arrs[i]表示当前叠加值加上该位置值 dp[i][j] = dp[i+1][j] || dp[i+1][j+arrs[i]];//选中和不选中方案 } } return dp[0][0]; } public static void main(String[] args) { int[] arr = {6,7,6,2,1}; int target = 4; boolean result = isSum(arr, 0, 0, target); //boolean result = isSumByDP(target, arr); System.out.println(result); } }

3、马拉车算法,

主要用来解决最长的回文串,重点是在于能扩到的边界,和这个以能扩到最右边界的中心点与当前所要求的的点的以这个点为中心点的对称点的回文串长度有没有超出范围。主要有三种情况要考虑。代码看链接里面。

4、BFPRT算法

解决TOP K的问题,比堆更低的时间复杂度。O(N)。这里我也不是很懂。
BFPRT算法步骤如下:
(1):选取主元;
  (1.1):将n个元素划分为⌊n5⌋个组,每组5个元素,若有剩余,舍去;
  (1.2):使用插入排序找到⌊n5⌋个组中每一组的中位数;
  (1.3):对于(1.2)中找到的所有中位数,调用BFPRT算法求出它们的中位数,作为主元;
(2):以(1.3)选取的主元为分界点,把小于主元的放在左边,大于主元的放在右边;
(3):判断主元的位置与k的大小,有选择的对左边或右边递归。

package basic_class_02;

public class Code_06_BFPRT {

	// O(N*logK)
	public static int[] getMinKNumsByHeap(int[] arr, int k) {
		if (k < 1 || k > arr.length) {
			return arr;
		}
		int[] kHeap = new int[k];
		for (int i = 0; i != k; i++) {
			heapInsert(kHeap, arr[i], i);
		}
		for (int i = k; i != arr.length; i++) {
			if (arr[i] < kHeap[0]) {
				kHeap[0] = arr[i];
				heapify(kHeap, 0, k);
			}
		}
		return kHeap;
	}

	public static void heapInsert(int[] arr, int value, int index) {
		arr[index] = value;
		while (index != 0) {
			int parent = (index - 1) / 2;
			if (arr[parent] < arr[index]) {
				swap(arr, parent, index);
				index = parent;
			} else {
				break;
			}
		}
	}

	public static void heapify(int[] arr, int index, int heapSize) {
		int left = index * 2 + 1;
		int right = index * 2 + 2;
		int largest = index;
		while (left < heapSize) {
			if (arr[left] > arr[index]) {
				largest = left;
			}
			if (right < heapSize && arr[right] > arr[largest]) {
				largest = right;
			}
			if (largest != index) {
				swap(arr, largest, index);
			} else {
				break;
			}
			index = largest;
			left = index * 2 + 1;
			right = index * 2 + 2;
		}
	}

	// O(N)
	public static 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 static int getMinKthByBFPRT(int[] arr, int K) {
		int[] copyArr = copyArray(arr);
		return select(copyArr, 0, copyArr.length - 1, K - 1);
	}

	public static 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 static 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 static 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, beginI, Math.min(end, endI));
		}
		return select(mArr, 0, mArr.length - 1, mArr.length / 2);
	}

	public static 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 {
				cur++;
			}
		}
		int[] range = new int[2];
		range[0] = small + 1;
		range[1] = big - 1;
		return range;
	}

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

	public static 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;
				}
			}
		}
	}

	public static void swap(int[] arr, int index1, int index2) {
		int tmp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = tmp;
	}

	public static void printArray(int[] arr) {
		for (int i = 0; i != arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
		// sorted : { 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 7, 9, 9, 9 }
		printArray(getMinKNumsByHeap(arr, 10));
		printArray(getMinKNumsByBFPRT(arr, 10));

	}

}

5、滑动窗口最大值

给定一个数组和一个固定大小的窗口,求这个窗口从左向右移动时,每次窗口中最大的数。重点在于如何更新窗口中的数。使窗口中存储单调递减的数。最左边保持最大的。当不更新了且达到滑动窗口的空间,就要开始丢弃最旧的数,也就是更新左边。

package niuke.zuoshen;

import java.util.LinkedList;

/**
 * 收集滑动窗口中的最大值个数
 * @author tangyuan
 *
 * 2019年6月21日
 * 

CopyRight:Copyright(c)2019

*/
public class GetMaxWindows { public static int[] getMaxWindows(int[] arr , int w) { int[] list = new int[arr.length - w + 1];//保存结果 LinkedList<Integer> deque = new LinkedList<>();//保存一个从左到右为从大到小的双端队列。值为数组的下标 int index = 0; for(int i = 0; i < arr.length; i++) { while(!deque.isEmpty() && arr[deque.peekLast()] <= arr[i])//如果右端的元素比当前要加入的元素下,就一直弹出 { deque.pollLast(); } deque.add(i);//将i加入 if(i - deque.peekFirst() == w)//将过期的删除,(值比当前的最右端小,且达到双端队列的最大值,则左边弹出) { deque.pollFirst(); } if(i >= w - 1)//当达到滑动窗口的值时,开始收集 { list[index++] = arr[deque.peekFirst()];//最大值收集出来 } } return list; } public static void main(String[] args) { int[] arr = {4,3,5,4,3,3,6,7}; int[] maxWindows = getMaxWindows(arr, 3); for(int i = 0; i < maxWindows.length; i++) { System.out.print(maxWindows[i] + " "); } } }

6、单调栈

单调栈结构解决不规则的连续图形,最大的矩形面积。主要也跟滑动窗口类似。保持一个递增的序列。当往栈中添加的不再是递增的就计算当前为高的,最后计算整个连续的递增序列的最大面积

package niuke.zuoshen;

import java.util.Stack;

/**
 * 给定一个二维数组,求其中为1的最大矩形面积
 * 
 * 
 * @author tangyuan
 *
 * 2019年6月22日
 * 

CopyRight:Copyright(c)2019

*/
public class MaximalRectangle { /** * 给定一个数组[4,3,5,6],每一个数代表一个矩形的高度,组成的一个二维数组,求其中的最大矩形 * * 解法,用最大单调栈的结构来求解,用来求解一个连续的无规则面积中最大的矩形面积 * @return */ public static int maxRecFromBottom(int[] height){ int maxArea = 0; if(height.length <= 0) return 0; Stack<Integer> stack = new Stack<>();//从小到大的单调栈 for(int i = 0; i < height.length; i++)//1、这一步是在求每次遇到不是单调递增的时候那个柱子的面积 { 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())//2、求整个单调递增的面积 { 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; } /** * 当传入的是一个二维数组的形式的时候 * [1,0,1,1] * [1,1,1,1] * [1,1,1,0] * [1,1,1,1] * * 解法就是将这个数组压缩成一维的形式 * @param map * @return */ public static int maxRecSize(int[][] map) { int maxArea = 0; if(map.length <= 0) return 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++) { if(map[i][j] != 0)//如果当前行不是0,则累加高度 height[j] += map[i][j]; else//如果当前行的值为0,则高度为0 height[j] = 0; } maxArea = Math.max(maxRecFromBottom(height), maxArea);//求出每一行的最大矩形面积 } return maxArea; } public static void main(String[] args) { int[] a = {4,3,5,6}; int[][] map = {{1,0,1,1}, {1,1,1,1}, {1,1,1,0}, {1,1,1,1}}; int maxRecSize = maxRecSize(map); System.out.println(maxRecSize); int result = maxRecFromBottom(a); System.out.println(result); } }

7、morris遍历

,优化递归和非递归的二叉树的遍历。重点在于利用了空节点的指针。分两种情况:一种是当前节点没有左节点了,则cur往右走,如果有,则往左走。morris相比较递归的优化重点在于他是对每个节点的访问只有两次。而递归是三次

package niuke.zuoshen;


public class MorrisTraversal {

	class Node{
		
		Node left;
		Node right;
		private int val;
		public Node(int val){
			this.val = val;
		}
	}
	/**
	 * 递归方式实现遍历二叉树,这种方法用到了log(n)的空间复杂度
	 * @param head
	 */
	public static void preTraversal(Node head){
		if(head == null)
			return ;
	//	System.out.println(head.val);//pre
		preTraversal(head.left);
		System.out.println(head.val);//in
		preTraversal(head.right);
	//	System.out.println(head.val);//order
	}
	/**
	 * morris遍历方式实现中序遍历二叉树,用到了log(1)的空间复杂度
	 * @param headnode
	 */
	public static void morris(Node headnode){
	
		if(headnode == null)
			return;
		Node cur = headnode;
		Node mostRight = null;
		while(cur != null)
		{
			mostRight = cur.left;
			if(mostRight != null)//当cur存在左孩子
			{
				while(mostRight.right != null && mostRight.right != cur)//来到最右边
				{
					mostRight = mostRight.right;
				}
				if(mostRight.right == null)//如果最右边指向空
				{
					mostRight.right = cur;//最右节点指向当前节点
					System.out.println(cur.val);//pre
					cur = cur.left;//当前位置往左走
				}
				else
				{
					mostRight.right = null;
				//	System.out.println(cur.val);//in
					cur = cur.right;
				}
			}
			else {//当cur不存在左孩子,则来到右节点
				System.out.println(cur.val);//pre & in
				cur = cur.right;
			}
		}
	}
	
	public static void main(String[] args) {
		
		Node root = new MorrisTraversal().new Node(1);
		Node two = new MorrisTraversal().new Node(2);
		Node three = new MorrisTraversal().new Node(3);
		Node four = new MorrisTraversal().new Node(4);
		Node five = new MorrisTraversal().new Node(5);
		Node six = new MorrisTraversal().new Node(6);
		Node seven = new MorrisTraversal().new Node(7);
		root.left = two;
		root.right = three;
		two.left = four;
		three.left = six;
		three.right = seven;
		two.right = five;
	//	preTraversal(root);
		morris(root);
		
	}
}

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

要有构造递归返回什么类型的值的方法,用返回值来保存信息。最后提取出我们所需要的结果。比如这个,要求是否是平衡二叉树。还应该保存高度,因为要判断左右子树的高度差。但我们最终的结果只需要是否是平衡的。

package niuke.zuoshen;


/**
 * 判断一颗数是否是平衡二叉树
 * @author tangyuan
 *
 * 2019年7月3日
 * 

CopyRight:Copyright(c)2019

*/
public class IsBalancedTree { /** * 二叉树的节点类型 * @author tangyuan * * 2019年7月3日 *

CopyRight:Copyright(c)2019

*/
public static class Node{ private int value; private Node left; private Node right; public Node(int value) { this.value = value; } } public static class ReturnType{ private int level;//高度 private boolean isBalance;//当前节点是否是平衡节点 public ReturnType(int level, boolean isBalance) { this.level = level; this.isBalance = isBalance; } } //采用构造返回值类型法 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.isBalance)//剪枝操作 return new ReturnType(level, false); ReturnType rightSubTreeInfo = process(head.right, level + 1); if(!rightSubTreeInfo.isBalance)//剪枝操作 return new ReturnType(level, false); if(Math.abs(rightSubTreeInfo.level - leftSubTreeInfo.level) > 1) return new ReturnType(level, false); return new ReturnType(Math.max(rightSubTreeInfo.level, leftSubTreeInfo.level), true); } public static boolean isBalance(Node head){ /*boolean[] res = new boolean[1]; res[0] = true; getHeigh(head, 1, res);*/ ReturnType process = process(head, 1); return process.isBalance; } private static int getHeigh(Node head, int level, boolean[] res) { if(head == null) return level; int lHeigh = getHeigh(head.left, level + 1, res); if(!res[0]) return level; int rHeigh = getHeigh(head.right, level + 1, res); if(!res[0]) return level; if(Math.abs(lHeigh - rHeigh) > 1) res[0] = false; return Math.max(lHeigh, rHeigh); } public static void main(String[] args) { Node head = new Node(1); head.left = new Node(2); head.right = new Node(3); head.left.left = new Node(4); head.left.right = new Node(5); head.right.left = new Node(6); head.right.right = new Node(7); System.out.println(isBalance(head)); } }

9、给定一个字符串代表一个表达式,其中包含括号,求其结果值

要注意的是每一个括号都是代表着一个子表达式,所以可以考虑用递归的方式来计算,将括号部分当成一个值来看,这样还是没有括号的求法。首先我们来看没有括号是怎么求的。

package niuke.zuoshen;

import java.util.LinkedList;
/**
 * 没有括号的求表达式的值
 * @author tangyuan
 *
 * 2019年7月3日
 * 

CopyRight:Copyright(c)2019

*/
public class ExpressionComputeNotBrackets { public static int process(String string ){ char[] ch = string.toCharArray(); int sum = 0; LinkedList<String> stack = new LinkedList<>(); for(int i = 0; i < string.length(); i++) { if(string.charAt(i) >= '0' && string.charAt(i) <= '9')//如果是数字,求出这个数字 sum = sum * 10 + string.charAt(i) - '0'; else{//遇到+ - * /的时候压入数字 addNum(sum,stack);//压入之前要先判断栈顶符号是不是* / stack.addLast(String.valueOf(string.charAt(i))); sum = 0; } } addNum(sum, stack);//最后一个数字 return getResult(stack); } private static int getResult(LinkedList<String> stack) { String cur = null; int result = 0; boolean addFlag = true; while(!stack.isEmpty()) { cur = stack.pollFirst(); if(cur.equals("+"))//如果是加号,表示下一个数是正的 addFlag = true; else if(cur.equals("-"))//负号表示下一个数是负的 addFlag = false; else//如果是数字 { int num = Integer.valueOf(cur); result += addFlag ? num:(-num); } } return result; } private static void addNum(int sum, LinkedList<String> stack) { if(!stack.isEmpty()) { String flag = stack.pollLast(); if(flag.equals("+") || flag.equals("-"))//遇到 { stack.addLast(flag); } else//遇到乘除,则先把乘除给处理了。 { int cur = Integer.valueOf(stack.pollLast()); sum = flag.equals("*")? (cur * sum) : (cur / sum); } } stack.addLast(String.valueOf(sum)); } public static void main(String[] args) { String string = "3+3*4+6/2+6"; int process = process(string); System.out.println(process); } }

再看包含括号的,其中对比可以发现只是多了一个递归过程的部分

package niuke.zuoshen;

import java.util.LinkedList;

/**
 * 给定一个字符串代表一个表达式,其中包含 + - * / ,求其计算结果
 * str = "3 + 3*(6+7*(3+1))+5"
 * @author tangyuan
 *
 * 2019年6月27日
 * 

CopyRight:Copyright(c)2019

*/
public class ExpressionCompute { /** * 解法,采用递归的方法,当遇到左括号的时候,进入递归子结构 * 子结构返回子结构的计算结果value和计算到的位置i * @param str * @return */ public static int getValue(String str){ char[] string = str.toCharArray(); int[] value = value(string, 0); return value[0]; } public static int[] value(char[] str, int i) { //双端队列模拟栈结构 LinkedList<String> que = new LinkedList<>(); 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';//统计一个数,比如310 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};//返回的是子结构部分的值 } //计算当前部分的结果,因为在add(num)的时候,已经将* /给计算了 private 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; } /** * 加入一个元素,加入之前判断栈顶的符号 是乘除的时候要处理掉 * @param que * @param num */ private 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 void main(String[] args) { String string = "3+3*(6+7*(3+1))+5"; int value = getValue(string); System.out.println(value); } }

10、一个数组中包含正、负、0的数,求其最长累加和为aim的子数组

一般遇到子数组问题,都要思考能不能求以每个数为结尾的结果。首先我们来思考另外一个相对简单的问题,就是如果数组中只包含正数的情况。因为数组中只包含了正数,所以每当添加一个数的时候结果就增大,当减小一个数的时候结果就减小,这样我们就可以用一个左右指针,表示当前累加的子数组,当累加的结果比aim小的时候就右边添加数字,当比aim大的时候,左边就删除数字。比如
arr[] = 2,3,4,1,9,6,7,aim = 7
刚开始的时候l = 0,r = 0代表当前累加和的子数组范围。因为目前累加和为sum=0,比aim=7小,所以继续添加数字r = 1,sum=5还是小于aim=7,继续添加r2,sum=9这时候比aim=7大,减少数字,l=1,sum=7。sum==aim,当sum=aim的时候,更新长度。知道最后的结果。

package niuke.zuoshen;
/**
 * 给定一个全为正数的数组arr,求和为num的最长子数组
 * @author tangyuan
 *
 * 2019年6月30日
 * 

CopyRight:Copyright(c)2019

*/
public class LongestSumSubArrayLengthForPositiveArray { /** * 采用窗口法,当当前窗口的和为num的时候,更新最长长度,然后左扩一位数或右减一位数 * @param arr * @param num * @return */ public static int longestSubArray(int[] arr, int num){ if(arr.length == 0 || arr == null) return 0; int left = 0;//左窗口指针 int right = 0;//右窗口指针 int sum = arr[0]; int resultLen = 0; while(right < arr.length) { if(sum == num) { resultLen = Math.max(resultLen, right - left + 1); right++; if(right >= arr.length) break; sum += arr[right]; } else if(sum < num) { right++; if(right >= arr.length) break; sum += arr[right]; } else { sum -= arr[left++]; } } return resultLen; } public static void main(String[] args) { int[] arr = {8,3,1,2,5,1,1,1,1,1,1}; int longestSubArray = longestSubArray(arr, 6); System.out.println(longestSubArray); } }

接下来我们来分析一下如果是包含正、负、0的情况。因为前面全是正数,添加一个数就变大,减少一个数就变小。但是现在不一样了,添加一个数和减少一个数的变化是不固定的。所以不能用一个窗口来做。而应该采用的是计算以每个位置为结尾的累加和。从左往右算,算的过程中可以查找是否存在某一段的累加和是否等于aim。而其中的计算思路就是用当前位置的累加和减去aim值。查看前面是否出现过某一段的累加和等于这个剩余值。如果存在,则说明扣掉前面这一段,从这个位置开始的累加和为aim.
arr[] = 7,3,2,1,1,7,-6,-1,7,aim = 7;
sum:要sum来进行累加从头开始到当前位置的累加和
hashMap:用来记录当前位置的累加和,key:到当前位置的累加和,value:为当前位置的下标

过程:
1、 sum = 0,sum - aim = -7,在hashMap中不存在。len = 0;hashMap = {0,-1};
2、 sum = 7,sum - aim =0,在hashMap中存在,len = Math.max(i - hashMap(sum - aim),len),len = 1;hashMap = {(0,-1),{7,0}};
3、 sum = 10,sum - aim =3,在hashMap中不存在,len = 1;hashMap = {(0,-1),(7,0),(10,1)};
4、 sum = 12,sum - aim =5,在hashMap中不存在,len = 1;hashMap = {(0,-1),(7,0),(10,1),(12,2)};
5、 sum = 13,sum - aim =6,在hashMap中不存在,len = 1;hashMap = {(0,-1),(7,0),(10,1),(13,3)};
6、 sum = 14,sum - aim =7,在hashMap中存在,len = Math.max(i - hashMap(sum - aim),len),len = 4;hashMap = {(0,-1),(7,0),(10,1),(14,4)};
7、 sum = 21,sum - aim =14,在hashMap中不存在,len = 4;hashMap = {(0,-1),(7,0),(10,1),(13,3),(21,5)};
8、 sum = 15,sum - aim =8,在hashMap中不存在,len =4;hashMap = {(0,-1),(7,0),(10,1),(13,3),(21,5),(15,6)};
9、 sum = 14,sum - aim =7,在hashMap中存在,len = Math.max(i - hashMap(sum - aim),len),len = 7;hashMap = {(0,-1),(7,0),(10,1),(13,3),(14,7)};
10、 sum = 21,sum - aim =14,在hashMap中不存在,len =7;hashMap = {(0,-1),(7,0),(10,1),(13,3),(21,5),(15,6),(14,7),(21,8)};

更新完毕,len = 7

package niuke.zuoshen;

import java.util.TreeMap;

/**
 * 给定一个数组,其中的元素可以正、负、0,求累加和为aim的最长子数组
 * @author tangyuan
 *
 * 2019年6月24日
 * 

CopyRight:Copyright(c)2019

*/
public class LongestSumSubArrayLength { /** * 当遇到最长子串的时候,应思考以每个位置为结尾的可能性,结果一定包含在其中 * @param arr * @param aim * @return */ public static int maxLength(int[] arr, int aim){ if(arr == null || arr.length == 0) return 0; TreeMap<Integer, Integer> treeMap = new TreeMap<>(); treeMap.put(0, -1); int len = 0; int sum = 0; for(int i = 0; i < arr.length; i++) { sum += arr[i]; //以当前位置结尾的累加和减去目标值剩下的还差多少,再减去前面的部分 //相当于sum是在计算累加和,而i-treeMap,get(sum - aim)在计算扣掉超过的前面部分 if(treeMap.containsKey(sum - aim)) { len = Math.max(i - treeMap.get(sum - aim), len);//更新位置 } if(!treeMap.containsKey(sum))//因为如果存在了,则最长部分会缩短,比如当前的累加和为7在 //i=0上,而现在计算出来的在i = 4这边,如果不判断,则会被覆盖掉 { treeMap.put(sum, i); } } return len; } public static void main(String[] args) { int[] arr = {7,3,2,1,1,7,-6,-1,7}; int maxLength = maxLength(arr, 7); System.out.println(maxLength); } }

11、判断一棵树是否是完全二叉树

根据完全二叉树的特性进行判断,完全二叉树的特性就是除了最后一层可以不满,其它层都必须是满节点,最后一层不能出现有右孩子而无左孩子。当某个节点出现没有右孩子的时候,后续节点都不能有孩子节点。

package niuke.zuoshen;

import java.util.LinkedList;

/**
 * 判断一颗数是否是完全二叉树
 * @author tangyuan
 *
 * 2019年7月7日
 * 

CopyRight:Copyright(c)2019

*/
public class IsCBTTree { public static class Node{ private Node left; private Node right; private int value; public Node(int value) { this.value = value; } } /** * 根据完全二叉树的特性,层次遍历每一个节点 * @param head * @return */ public static boolean isCBT(Node head){ LinkedList<Node> queue = new LinkedList<>();//用来存放节点,采用层次遍历的方法 Node left = null; Node right = null; queue.offer(head); boolean leaf = false; while(!queue.isEmpty()) { Node node = queue.poll(); left = node.left; right = node.right; //条件1:当已经将最后一个有孩子节点位置,后面的节点不能出现有孩子 //条件2:不能出现有右孩子没有左孩子 if((leaf && (left != null || right != null)) || (left == null && right != null)) return false; if(left != null) queue.offer(left); if(right != null){ queue.offer(right); }else//当没有右节点的时候,则说明来到最后一个节点了 { leaf = true; } } return true; } public static void main(String[] args) { Node node1 = new Node(1); Node node2 = new Node(2); Node node3 = new Node(3); Node node4 = new Node(4); Node node5 = new Node(5); Node node6 = new Node(6); Node node7 = new Node(7); node1.left = node2; node1.right = node3; node2.left = node4; node2.right = node5; node3.left = node6; node3.right = node7; boolean cbt = isCBT(node1); System.out.println(cbt); } }

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