• 剑指offer所有题目详解,可访问我的github项目:KongJetLin-offer

  • 目录

  1. Number26:二叉搜索树与双向链表
  2. Number27:字符串的排列
  3. Number28:数组中出现次数超过一半的数字
  4. Number29:最小的k个数
  5. Number30:连续子数组的最大和

题目26 二叉搜索树与双向链表

  题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

  分析:
  1)要求双向链表排序,我们利用二叉搜索树的特性,中序遍历二叉搜索树,就可以得到排序的结点。
  2)我们从二叉搜索树取出一个结点构建链表,设置该结点的左指针指向前一个结点,右指针指向下一个结点,其实我们在将取出的结点连接到双向链表的时候,只需要考虑前一个结点的右指针指向当前新的结点、当前新结点的指针指向前一个结点即可。
  至于当前新结点的右指针不需要管,如果有下一个结点插入双向链表,那么当前新结点的指针再设置指向下一个结点;如果没有下一个插入,当前结点的右指针则什么都不指向(null),此时已经是原来二叉搜索树中序遍历的最后一个结点。
  其实对于第一个结点和最后一个结点,他们在二叉搜索树中左右指针本来都指向null,因此他们有一边不指向任何结点并不会影响链表结构!
  有疑问就在大脑里面想象一下整个过程即可!
  代码如下:

private TreeNode head;//创建一个头结点,用于返回双向链表头结点//由于插入一个链表,我们只需要考虑当前结点左指针 和 前一个结点右指针,那么我们只需要创建一个代表前一个结点的pre即可private TreeNode pre;public TreeNode Convert(TreeNode pRootOfTree){//使用中序遍历方式找出二叉搜索树所有结点,并一个个连接到双向链表上inOrder(pRootOfTree);return head;//返回双向链表头结点}private void inOrder(TreeNode node){if(node == null)return;//如果二叉搜索树结点遍历完,不需要再向双向链表添加结点,直接结束递归//中序遍历左子树inOrder(node.left);/*对于遍历到的当前结点,我们先判断前一个结点pre是否存在,如果不存在,说明在双向链表中,当前结点是第一个结点,将当前结点赋予pre;如果存在,说明在双向链表中,当前结点前面有结点,将pre.right指向当前结点node,将node.left指向pre,这样当前结点就连接到双向链表,此时,对于下一个插入的结点当前结点就是前一个结点,因此将pre指向node,便于插入下一个结点。另外,如果pre=null,说明node是双向链表第一个结点,将其赋予head*/if(pre != null){pre.right = node;node.left = pre;pre = node;}else{pre = node;head = node;}//中序遍历右子树inOrder(node.right);}

题目27 字符串的排列

  题目描述:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
  输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

  • 注:这里参考文章添加链接描述

  分析:我们可以把整个流程分为2步:
1)求所有可能出现在第一个位置的字符,即把第一个字符与后面的字符依次交换。(for循环)
2)将某一个字符固定,求后面所有字符的排列。
  而求后面所有字符的排列,我们仍然可以将其分解为上面2步。这是一个递归的过程,直到字符串的结尾,我们停止递归。
  如下图:

  注意点:
1)在每个分支进行完后,要将交换过的元素复位,从而不会影响其他分支;
2)题目指出可能有字符重复,所以分支末尾可能有重复的序列,在加入ArrayList要进行去重判断,不重复再加入。

  代码如下:

public class OfferGetTest27
{//将返回结果的ArrayList定义在外面,避免下面的函数传递太多的参数ArrayList<String> result = new ArrayList<>();public ArrayList<String> Permutation(String str){if(str==null || str.length()==0)return result;//注意,这里返回result这个ArrayList,不要返回null。因为当 str=""的时候,返回ArrayList,里面也是"",而不是null!Permutation(str.toCharArray() , 0);//index从0开始Collections.sort(result);//最后,将ArrayList的字符串排列一下,因为测试用例的字符串是有顺序的,不排列最后无法通过!return result;}//从strArr字符串数组的index位置开始置换字符private void Permutation(char[] strArr , int index){/**当 index=strArr.length,说明递归到字符数组的结尾,需要将这个排列的字符数组转换为字符串,当然,由于题目给定的字符串里面可能有重复的字符,我们这里获取字符有可能与前面获取到的字符重复,因此在放入 result 之前需要先判断 result 里面是否存在这个字符串。*/if(index == strArr.length-1)//遍历到最后一个字符,不需要再替换后面的字符(也不需要替换自己){String str = String.valueOf(strArr);//将这个顺序的字符数组变为字符串if(!result.contains(str))result.add(str);//字符串不存在才将其加入result}else{/**如果没有递归到字符数组的末尾,我们将index位置的字符逐个与后面的字符替换,然后将当前index位置的字符固定,通过 Permutation() 方法,递归寻找index+1位置开始的所有字符的可能排列,直到递归到字符数组的末尾。注意:1)这里需要从index位置开始替换,即index位置的字符保持不变,因为这也是一种情况;2)当index+1位置递归返回后,我们需要将原来的替换复原,如果不一层一层递归在替换后都复原,递归后的字符数组就是乱序的,从而影响其他分支的替换。(这个过程参考解析的图)*/for (int i = index; i <= strArr.length-1 ; i++){//index位置的字符在循环中逐个替换 i=index,index+1,...,strArr.length-1位置的字符swap(strArr , index , i);//替换一个字符后,将当前index位置的字符固定,从index+1位置开始递归替换Permutation(strArr , index+1);/**为了不影响其他分支,我们在递归搜索完 index+1 开始位置所有可能的字符排列情况后,将替换还原,而递归中也会将替换还原,那么到这里,就变成没有替换 index与i 位置字符之前的情况,那么我们下面就可以将 index与i+1 进行替换,这样就可以找到index位置所有可能的字符(想象一下这个过程!)*/swap(strArr , index , i);//另外,这里不需要考虑重复,因为递归到最后,重复的字符串会被剔除}}}//替换方法private void swap(char[] arr , int p1 , int p2){char temp = arr[p1];arr[p1] = arr[p2];arr[p2] = temp;}public static void main(String[] args){ArrayList<String> ab = new OfferGetTest27().Permutation("ab");for (String s : ab){System.out.println(s);}}
}

题目28 数组中出现次数超过一半的数字

  题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

  多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。
  使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt++,否则令 cnt–。如果前面查找了i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于i / 2 的话 cnt 就一定不会为 0 。
此时我们用于统计的元素,有可能是majority,也有可能不是majority,但是剩下的元素中,肯定有majority,且剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找剩下的元素,就能找出 majority。
  当然还可以使用复杂度为O(n^2)的暴力法,不赘述!

public int MoreThanHalfNum_Solution(int [] array){//我们使用数组第一个元素作为比较元素int comNum = array[0];/*** 使用count来计算当前比较元素的个数,初始值为1个,设数组长度是n* 1)当count=0的时候,说明前i+1个数组元素中(数组元素从0开始),比较元素的个数等于其他元素的个数。此时比较元素可能是majority,也有可能不是majority。* 如果比较元素是 majority,由于 majority 大于n/2,那么后面的 n-i-1 个元素中,majority的数量肯定大于一半;* 如果比较元素不是 majority,其他元素(包括majority)个数小于此时的比较元素,那么后面的 n-i-1 个元素中,majority的数量肯定大于一半;* 因此我们可以将比较元素进行替换:comNum = array[i]; ,继续从 i+1 开始,进行下面的遍历。* 遍历到数组最后,一定会有count一直大于0的情况,此时的比较元素就是majority。** 2)替换的时候,为什么不把比较元素替换为:array[i+1],不是说从第 i+1 遍历比较吗?* 其实这里可以替换为 comNum = array[i+1],但是我们下一次循环又会遇到 array[i+1],再次比较就出错。(其实可以设置)* 因此我们此时将比较元素设置为:array[i],* 其实我这里考虑的是,会不会因为从 i 开始而不是从 i+1 开始,导致从i到n-1的元素中majority个数小于一半。*  其实不会,因为如果array[i]是majority,那么从i到n-1的元素中,majority的个数肯定大于一半;* 如果array[i] 不是majority,很快它遇到与他不同的元素(遇到的有可能是majority,也有可能不是majority),* 遇到的元素会使得比较元素再次替换,那么剩下的元素中majority的数量肯定大于一半。* 例如:* i+1 与 i 不同,那么此时比较元素替换为 array[i+1],那么从i+1到n-1的元素中,majority的个数肯定大于一半;* 如果 i+1 与 i 相同,很快就会大量不同的其他元素,那么剩下的元素中majority的个数还是大于一半!!!*/for (int i = 1,count = 1; i < array.length ; i++){if(array[i] == comNum)count++;elsecount--;if(count==0){//将array[i]设置为比较元素,注意设置count=1,即此时比较元素个数为1comNum = array[i];count = 1;}}/*结束循环的时候,我们会获取到最后的comNum,此时这个comNum的count>=1,但是我们无法提供count的值进行判断;此时如果原来的数组中存在majority,那么comNum在数组中的数量肯定大于 n/2,且comNum就是majority;如果如果原来的数组中不存在majority,那么comNum在数组中的数量肯定不于 n/2;我们计算 comNum在数组中的数目,进行比较即可。*/int num = 0;for (int i : array) {if(i == comNum)num++;}return num>array.length/2 ? comNum : 0;}

题目28 数组中出现次数超过一半的数字

  题目描述:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

  使用快排、计数排序、最大堆优先队列实现,各个代码如下:
  最大堆,时间复杂度是:O(nlogn)

public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k){ArrayList<Integer> res = new ArrayList<>();//注意对特殊情况进行判断。if(input == null || input.length == 0 || k > input.length || k <= 0)return res;//记住java实现最大堆用最小堆加上Lambda表达式:(o1, o2) -> o2-o1  即可Queue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2-o1 );//将数组中的元素放入堆中(注意只放k个元素)for (int i = 0; i < input.length; i++){/**特别注意,这里 queue.size() < k,而不是queue.size() <= k。在添加第k个之前,queue.size()=k-1<k,进入循环,添加第k个,随后 queue.size() =k跳出循环,刚刚好添加k个。如果 queue.size() <= k,在添加k个后还满足循环,会添加第k+1个!*/if(queue.size() < k){queue.add(input[i]);}else{if(input[i] < queue.peek()){//将队首(堆顶)元素移除,将更小的元素加入堆queue.remove();queue.add(input[i]);}}}

  快排,时间复杂度:O(nlogn)

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k){ArrayList<Integer> res = new ArrayList<>();if(input == null || input.length==0 || k>input.length || k<=0)return res;quickSort(input , 0 ,input.length-1);for (int i = 0; i < k ; i++){res.add(input[i]);}return res;}private void quickSort(int[] arr , int left , int right){//当 left>= right 的时候,结束递归if(left >= right)return;if(left < right){//找去区间 left到right的中轴元素下标mid,并将小于中轴元素的元素放在mid左边,大于他的放在mid右边int mid = partition(arr , left , right);quickSort(arr , left , mid-1);quickSort(arr , mid+1 , right);}}private int partition(int[] arr , int left , int right){int pivot = arr[left];//随机取最左边的元素为中轴元素int start = left+1;int end = right;while (true){//找到左区间第一个大于 pivot 的元素while (start<=end && arr[start]<=pivot)start++;//找到右区间第一个小于于 pivot 的元素while (start<=end && arr[end]>pivot)end--;//若start>end ,说明中轴元素左右两边元素摆放完毕,结束循环if(start>end)break;//如果没有跳出循环,则交换start与end的元素swap(arr , start ,end);}//跳出循环后,此时end为中轴元素应该放的位置,将left与end互换元素,并放回中轴元素下标:endswap(arr , left ,end);return end;}private void swap(int[] arr , int i , int j){int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}

  计数排序,时间复杂度:O(n+k),这会多花费:O(k)的空间,其中k是新数组的大小。

ArrayList<Integer> res = new ArrayList<>();if (input == null || input.length == 0 || k > input.length || k <= 0) return res;//1、先找出input最大值最小值int min = input[0];int max = input[0];for (int i = 1; i < input.length ; i++){if(input[i] < min)min = input[i];if(input[i] > max)max = input[i];}//2、随后,求出临时数组的大小,并构建临时数组int length = max-min+1;int[] temp = new int[length];//3、遍历input数组,将input数组元素作为temp数组下标,个数为特莫数组下标元素对应的值for (int i = 0; i < input.length ; i++){//注意,藤牌数组区间从 0到max-min,而input数组元素从 min到max,因此应该减去mintemp[input[i]-min]++;}int index = 0;//将temp元素取出放入inputfor (int i = 0; i < temp.length ; i++){while (temp[i]>0){input[index++] = i + min;//注意将min加回去temp[i] --;}}for (int i = 0; i < k ; i++){res.add(input[i]);}return res;

题目30 连续子数组的最大和

  题目描述:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

方法1:暴力法
  如下,暴力遍历所有序列并对比,时间复杂度是O(n^2)

public static int FindGreatestSumOfSubArray1(int[] array) {//maxSum不能赋值为0,有可能整个数组都是负数//maxSum赋值为最开始的子序列array[0],如果没有找到比他更大的子序列,array[0]就是最大的子序列,否则就赋值为其他更大的子序列int maxSum = array[0];//外循环用于调整从哪个下标开始,内循环用于记录以这个下标为开始的所有子序列的和for (int i = 0; i < array.length ; i++){//thisSUm 循环记录所有以j下标为开头的子序列的值,当找到某一个子序列 thisSum>maxSum 的时候,我们就将这个子序列的值赋予maxSum//由于下一个内循环开始之前,开始下标变换,因此thisSum必须重新赋值为0int thisSum = 0;for (int j = i; j < array.length ; j++){thisSum += array[j];if(thisSum > maxSum)maxSum = thisSum;}}return maxSum;//运行时间:12ms,占用内存:9412k}

方法2:动态规划(推荐)
  先上动态规划的公式:

max(dp[i]) = getMax( max(dp[i-1]) + arr[i] , arr[i] )

  首先,dp[i] 就是以数组下标为 i 的数做为结尾的最大子序列和,注意是以 i 为结尾

比如说现在有一个数组 arr = {6,-3,-2,7,-15,1,2,2},我们使用一个 maxSum 来记录当前最大的子序列。dp[0] 就是以下标0为结尾的子序列的最大和,显然只有一个,我们记为 dp[0] = arr[0] = 6,此时maxSum = dp[0]。dp[1] 就是以下标1为结尾的子序列的最大和,那么 dp[1] = Max{dp[0]+arr[1] , arr[1]},既如果 dp[0]<0 ,dp[1]=arr[1],否则dp[1]=dp[0]+arr[1]。我们求出以下标1位结尾的子序列的最大和后,判断dp[1]与maxSum的大小,如果dp[1]>maxSum,我们将maxSum=dp[1]。....dp[n] 就是以下标n为结尾的子序列的最大和,那么 dp[n] = Max{dp[n-1]+arr[n] , arr[n]},既如果 dp[n-1]<0 ,dp[n]=arr[n],否则dp[n]=dp[n-1]+arr[n]。我们求出以下标n位结尾的子序列的最大和后,判断dp[n]与maxSum的大小,如果dp[n]>maxSum,我们将maxSum=dp[n]。.....结论:1)我们只需要遍历数组,找到数组所有下标对应的以该下标为结尾的子序列的最大和,这就相当于判断完了数组中所有的子序列。并且,我们使用maxSum保存了这些子序列的最大值。2)对于 dp[n]:以下标n为结尾的子序列的最大和,我们必须先找到dp[n-1]:以下标n-1为结尾的子序列的最大和,dp[n-1]已经将前面的所有情况覆盖并找到最大和,此时dp[n]的最大值只有两种情况:dp[n-1]+arr[n] 或者 arr[n]。

  动态规划法的时间复杂度是O(n)。
  实现代码如下:

public static int FindGreatestSumOfSubArray2(int[] array) {//注意,maxSum目前也应该记录为array[0],不能是0,因为可能整个数组值都小于0!int maxSum = array[0];//用于记录最大子序列和.int thisSum = array[0];//用于记录 以数组当前下标为结尾的最大子序列和,我们将其初始化赋值为dp[0]//从dp[1]开始查找for (int i = 1; i <array.length ; i++){if(thisSum < 0)//如果dp[n-1]<0thisSum = array[i];//更新dp,此时dp[n]=array[n]else//如果dp[n-1]>0thisSum += array[i];//更新dp,此时dp[n]=array[n]+dp[n-1]//更新maxSUm值,最后才能得到数组中的最大子序列if(maxSum<thisSum)maxSum = thisSum;//当当前下标的dp[n]比 以之前下标结尾的最大子序列要大,那么就将maxSum更新为dp[n]}return maxSum;//运行时间:14ms,占用内存:9396k}

  还有一种递归分治的方法,参考文章:添加链接描述
  当然这篇文章的方法是对的,但是里面有的步骤写错,可以参考我在文章评论指出的错误!

剑指offer刷题详细分析:part6:26题——30题相关推荐

  1. 【LeetCode 剑指offer刷题】树题16:Kth Smallest Element in a BST

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) Kth Smallest Element in a BST Given a binary search tree, ...

  2. 【LeetCode 剑指offer刷题】查找与排序题12:Top K Frequent Elements

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) Top K Frequent Elements Given a non-empty array of integer ...

  3. 【LeetCode 剑指offer刷题】树题19:8 二叉树中序遍历的下一个结点

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) 8 二叉树中序遍历的下一个结点 题目描述 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回.注 ...

  4. 【LeetCode 剑指offer刷题】树题6:28 对称二叉树(101. Symmetric Tree)

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) 101. Symmetric Tree /**  * Definition for a binary tree no ...

  5. 【LeetCode 剑指offer刷题】数组题2:57 有序数组中和为s的两个数(167 Two Sum II - Input array is sorted)...

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) 57 有序数组中和为s的两个数 题目描述 输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是 ...

  6. 【LeetCode 剑指offer刷题】字符串题6:67 把字符串转成整数

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) 67 把字符串转成整数 题目描述 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数. 数值为0或者字符 ...

  7. 【LeetCode 剑指offer刷题】回溯法与暴力枚举法题6:Number of Islands

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) Number of Islands Given a 2d grid map of '1's (land) and ' ...

  8. 【LeetCode 剑指offer刷题】查找与排序题14:Wiggle Sort(系列)

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) Wiggle Sort II Given an unsorted array nums, reorder it su ...

  9. 【LeetCode 剑指offer刷题】矩阵题1:4 有序矩阵中的查找( 74. Search a 2D Matrix )(系列)...

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) 74. Search a 2D Matrix Write an efficient algorithm that s ...

  10. 【LeetCode 剑指offer刷题】字符串题12:Valid Palindrome(回文词系列)

    [LeetCode & 剑指offer 刷题笔记]目录(持续更新中...) Valid Palindrome Given a string, determine if it is a pali ...

最新文章

  1. 004 .NetReactor 3.6.0.0之另类脱壳法
  2. 微软2021校园招聘正式启动
  3. python linux 优化_Linux性能优化(一)
  4. 怎样设计访谈提纲_服务设计简史
  5. Android自己写的三款实用开关控件
  6. 杭电多校HDU 6579 Operation (线性基 区间最大)题解
  7. 计算机学院金海教授当选2019IEEE会士
  8. NSIS安装vcredist_64.exe
  9. [转]overflow解决float浮动后高度自适应问题
  10. 揭秘0撸资金盘:区块链噱头,拉人头模式,数亿元收割
  11. 计算机wordif函数,wordif函数怎么用
  12. 【安全牛】一起来打靶第一周笔记
  13. MySQL使用存储过程批量插入百(千)万测试数据
  14. Tushare介绍和入门级实践(1)——使用tushare接口获取沪深300成分股交易日涨跌数据
  15. 深度学习-根据名字识别男女
  16. 本体李俊火星大学最新演讲:从区块链核心价值谈金融场景应用
  17. 计算机动漫设计需要学什么,计算机动画制作专业好学吗?都有哪些课程?
  18. python在excle教程全集_Python对Excel操作教程
  19. C公司的产品项目:一家组织架构参考
  20. Java游戏开发 —— 坦克大战

热门文章

  1. 简单注册、登录、修改密码、忘记密码程序
  2. err_code啥意思?
  3. SQL server 2012 附加数据库
  4. 让网页成为蜘蛛网-SEO优化总结
  5. Python入门-基础篇笔记02
  6. 单隐层BP神经元个数对迭代步数和预测误差的影响
  7. 【算法练习】85.差的绝对值为 K 的数对数目——计数
  8. 阿里+中科院提出:将角度margin引入到对比学习目标函数中并建模句子间不同相似程度...
  9. 基于51单片机 超声波测距 倒车雷达
  10. Class文件格式实战:使用ASM动态生成class文件