归并排序

归并排序本身是一个递归的过程,在每一次递归的过程中,将数组分成等长的2部分,然后左边进行递归,然后右边跟着递归,然后进行一个排序,这个排序的过程的前提是左右部分都是有序的,然后左右两边的数据分别从头部进行比较,小的数就存入一个新的数组中,并且相应的位置后退一步,这样下来可以使左右两边的数据全部有序的进入,当有一边的数已经全部进入数组,那么直接把剩下的那边依次放入数组就完成了。

  1. public class mergesort {
  2. /*
  3. * 如何进行归并排序?
  4. * 1.首先传入一个数组,然后我们把他分开2分,然后这2分进行递归,递归到结束的条件就是我们分开后的2分中只有
  5. * 一个元素,然后让这两个元素进行排序,排序的过程是两边的数从头开始比较,并且开辟一个新的数组,小的数先进数组,大的数后进入,
  6. * 当一侧的数据读完后,就将剩余那部分的数全部放入数组,然后在将排好序的数,重新放回最初传进来的数组,一层层递归结束后,就是
  7. * 排好顺序的数了
  8. *
  9. * */
  10. //1.先判断这个数组是否为空或者只有一个元素
  11. //解释一下为什么没有返回值,因为传入的参数是一个数组,我们在排序中会对数组中的
  12. //元素进行排序,所以说传入后,即使不返回,数组也变了
  13. static void mergesort(int[] arr){
  14. if (arr==null||arr.length<=1){
  15. return ;
  16. }
  17. mergesort(arr,0,arr.length-1);
  18. }
  19. //如果不为空就进入正式开始的递归排序,这里传入的参数,第一个是数组,第二个是开始位置,第三个是结束位置
  20. static void mergesort(int[] arr,int start ,int end){
  21. //先声明递归结束的条件
  22. if (start==end){
  23. return ;
  24. }
  25. //如果不是就2分,先找到中间点
  26. int mid = (start+end)/2;
  27. //那么分开的部分进行递归的开始和结束位置分别是start mid 和 min+1 end
  28. mergesort(arr, start, mid);
  29. mergesort(arr,mid+1,end);
  30. //关键在于这个merge,这个merge要怎么写?
  31. merge(arr,start,mid,end);
  32. }
  33. static void merge(int[] arr,int start,int mid,int end){
  34. //传进来了三个参数,然后依次进行归并排序
  35. //首先要有2个指针,分别指向2边的开头,然后比较结束的条件就是任意一方的数读到边界!
  36. int[] newarray = new int[arr.length];
  37. int i = start;
  38. int p1 = start;
  39. int p2 = mid+1;
  40. while((p1<mid+1)&&(p2<end+1)){
  41. //这句话的意思就是两边指针所指向的位置进行比较,如果
  42. //小的一边对数组进行赋值,指针向后移动一位
  43. newarray[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
  44. }
  45. while(p1<mid+1){
  46. newarray[i++] = arr[p1++];
  47. }
  48. while(p2<end+1){
  49. newarray[i++] = arr[p2++];
  50. }
  51. //拷贝回去
  52. for(int j = start;j<=end;j++){
  53. arr[j] = newarray[j];
  54. }
  55. }
  56. public static void main(String[] args) {
  57. int[] arr = new int[]{2,5,4,1};
  58. mergesort(arr);
  59. for (int i : arr) {
  60. System.out.println(i);
  61. }
  62. }
  63. }

问题拓展,小和问题,求逆序对

记不住了!

快速排序

荷兰国旗问题!

什么是荷兰国旗问题?首先我们先看荷兰国旗
image.png

由三种颜色组成,所以我们今天要研究的就是给定一个数组,和一个数num,对数组进行排序,把大于在这个数的数放在右边,小于这个数的数放在数组左边,等于这个数的数放在中间,当然,在大于和小于的部分,我们不要求有序。

所以我们要思考怎么去做?(要求额外的空间复杂度是O(1)),只能用有限制的几个变量进行操作
首先划分2个边界,一个是小于边界,一个是大于边界,小于边界在数组的最左边,大于边界在数组的最右边,我们根据一下的3条规则进行操作

1.数组从左向右遍历,遇到比num小的数,我们将它与小于边界的数进行交换,然后小于边界向后移动一位,

2.当遇到等于num的数,我们的不进行任何操作,继续遍历

3.当遇到大于num的数,将大于边界所指向的数和它进行交换,然后大于边界向前移动一位。然后这次循环重新进行

终止的条件就是我们遍历的数到达了大于的边界就停止遍历。

  1. public class NetherlandsFlag {
  2. static void partion(int[] array,int a,int b,int nums){
  3. int left = a;
  4. int right= b;
  5. int i = a;
  6. //确定左右边界,可以直接却认为数组的开头和结尾(长度减一)
  7. while (i<=right) {//只要没有到达最大数的边界就一直遍历下去,3种条件
  8. //1.比num小
  9. if (array[i]<nums){
  10. //进行交换操作,交换的对象,当前数和小于边界所指向的数,执行结束后i++,left++
  11. int temp = array[i];
  12. array[i] = array[left];
  13. array[left] = temp;
  14. i++;
  15. left++;
  16. }
  17. //等于num
  18. else if(array[i]==nums){
  19. i++;
  20. }
  21. //大于nums
  22. else{
  23. int temp = array[i];
  24. array[i] = array[right];
  25. array[right] = temp;
  26. right--;
  27. }
  28. }
  29. }
  30. public static void main(String[] args) {
  31. int[] array = new int[]{0,7,8,5,5,9,5,4,5,3};
  32. partion(array,0,array.length-1,5);
  33. for (int i : array) {
  34. System.out.println(i);
  35. }
  36. }
  37. }

快速排序

快速排序就是以荷兰国旗排序为基础,这次只给一个数组,我们进行排序,那么参照荷兰国旗,我们可以首先选取数组最后一个数作为num,然后对数组中的数进行排序,分为,小于,等于,大于三部分,排序之后我们将大于区域的最前面的数和num的位置进行交换,然后以num的左右边界的最后一个数作为nums,进行新一轮的排序,这样递归下来我们最终就可以确定出所有数的正确顺序,每次确定一个数。

  1. public class NetherlandsFlag {
  2. static int[] partion(int[] array,int a,int b,int nums){
  3. int left = a;
  4. int right= b;
  5. int i = a;
  6. //确定左右边界,可以直接却认为数组的开头和结尾(长度减一)
  7. while (i<=right) {//只要没有到达最大数的边界就一直遍历下去,3种条件
  8. //1.比num小
  9. if (array[i]<nums){
  10. //进行交换操作,交换的对象,当前数和小于边界所指向的数,执行结束后i++,left++
  11. int temp = array[i];
  12. array[i] = array[left];
  13. array[left] = temp;
  14. i++;
  15. left++;
  16. }
  17. //等于num
  18. else if(array[i]==nums){
  19. i++;
  20. }
  21. //大于nums
  22. else{
  23. int temp = array[i];
  24. array[i] = array[right];
  25. array[right] = temp;
  26. right--;
  27. }
  28. }
  29. //这里的right永远指向的是下一个未知的数,所以返回最后一个确定的最大值的位置,那么就返回right-1,左边的就是left-1
  30. return new int[]{left-1,right+1};
  31. }
  32. static void quicksort(int[] arr,int start,int end){//最初传递的数组肯定是从0到末尾,首先确定出末尾位置
  33. int num = arr[end];
  34. //然后剩下的部分进行排序,传入数组的起点,重点 和 num
  35. int[] res = partion(arr, start, end - 1, num);//拿到交换的位置
  36. int temp = arr[end];
  37. arr[end] = arr[res[1]];
  38. arr[res[1]] = temp;
  39. //交换完毕,递归左右两边,右边的起点是partion+1 ,左边的起点没返回,
  40. if (res[0]>=start){
  41. quicksort(arr,start,res[0]);
  42. }
  43. if (res[1]<end){
  44. quicksort(arr,res[1]+1,end);
  45. }
  46. }
  47. public static void main(String[] args) {
  48. int[] array = new int[]{0,7,8,5,5,9,5,4,5,3};
  49. quicksort(array,0,array.length-1);
  50. for (int i : array) {
  51. System.out.println(i);
  52. }
  53. }
  54. }

堆排序

  1. package com.zjl.heap;
  2. import java.lang.Math;
  3. public class heap {
  4. //堆排序
  5. static void heapsort(int[] arr){
  6. //首先建堆
  7. heapinsert(arr);
  8. //不断的调整;
  9. for (int i = arr.length-1;i>0;i--){
  10. heapify(arr,0,i);
  11. }
  12. }
  13. //建立一个堆
  14. static void heapinsert(int[] arr){
  15. /*怎么建立,传入一个数组,有一个表示当前堆大小的指针
  16. * 每次我们读取一个数,然后调整一下看是否满足堆的定义,然后指针后移*/
  17. //判断是否满足堆。判断之后要继续数组数据
  18. for(int i = 0;i<arr.length;i++){
  19. int heapsize = i;
  20. while(arr[heapsize]>arr[(heapsize-1)/2]){
  21. //进行交换
  22. int temp = arr[heapsize];
  23. arr[heapsize] = arr[(heapsize-1)/2];
  24. arr[(heapsize-1)/2] = temp;
  25. //交换位置之后,继续判断,heapsize改变,这里要用另一个指针;
  26. heapsize = (heapsize-1)/2;
  27. }
  28. }
  29. }
  30. //堆化,首先我们的本质思想是拿走最顶上得数,用最后一个数替代,比如说已经建好的堆在这块
  31. static void heapify(int[] arr, int position, int heapsize){
  32. int size = heapsize;
  33. //本质就是堆顶和堆最后一个进行交换;
  34. int temp = arr[position];
  35. arr[position] = arr[size];
  36. arr[size] = temp;
  37. int i = position;
  38. //代表我们已经把最大数的一部分给掩盖住了
  39. //然后堆化,遵从3条规则 1.这个位置先判断有没有左右孩子,没有就算了,
  40. //有的话找出左右孩子中的较大的一
  41. // 个,2.和当前的结点进行比较,当前大不动,不然就交换
  42. //,然后从交换位置再次开始这次操作;
  43. while((2*i+1)<size){//代表一定有左孩子
  44. int max = arr[2*i+1];
  45. int index = 2*i+1;
  46. //判断有没有右孩子
  47. if ((2*i+2)<size){
  48. max = Math.max(arr[2*i+1],arr[2*i+2]);
  49. index = arr[2*i+1]>arr[2*i+2]?2*i+1:2*i+2;
  50. }
  51. //然后和父节点比较
  52. if (arr[i]<max){
  53. //这里要进行交换,但是没有索引;
  54. temp = arr[i];
  55. arr[i] = arr[index];
  56. arr[index] = temp;
  57. i = index;
  58. }
  59. //否则这就是这个位置应该放置的元素;
  60. else{
  61. break;
  62. }
  63. }
  64. }
  65. public static void main(String[] args) {
  66. int[] arr = new int[]{5,2,1,3};
  67. heapsort(arr);
  68. for (int i : arr) {
  69. System.out.println(i);
  70. }
  71. }
  72. }

链表汇总

基础常识

链表是一种逻辑上连续的数据结构,我们只能通过当前结点找到下一个结点,不能像数组一样快速的听过下标找到指定的位置。所有链表具有增删快,查找慢的特点。

五道题

这里将汇总在LeetBook上出现的几个比较经典的链表题目
image.png
这道题就很有趣,因为链表是没有下标的,所以我们在遍历的时候,是不知道链表的长度的,或者说只有走一遍才能知道到底有多少个Node,然后再次遍历得出所找的结点。当然这样做是不够快的,应该追求一次遍历就出结果,我们这里就要提一提双指针的问题了;所谓双指针,就是在刚开始有2个指针都指向链表的头部,然后我们可以让其中一个先走几步,然后再让第二个和第一个一起走,这样当第一个走到终点的时候,第二个指针就会距终点一定的距离,这个距离就是我们第一个指针先走出来的长度,所以如果说,我们求倒数第一个,那就先走一步,到最后我们的慢指针就会指向倒数第一个的前一个位置,然后进行p.next = p.next.next;操作进行删除即可;这就是大致思路,但是这个问题还是有细节需要我们注意的;
1.如果只有一个结点,并且删除第一个,而且p.next = p.next.next;这句话也会报错。(会很难处理,产生空指针异常)所以说为了解决这个问题,我们可以引出一个新的链表头,把head这个头结点在前置一个结点,然后快慢指针都从这个位置出发,我们就可以按照这个思路快速的解决问题了;

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode removeNthFromEnd(ListNode head, int n) {
  13. ListNode p = new ListNode();
  14. //这是在头结点前面又加了一个结点,用于作为快慢两个指针的出发点;
  15. p.next = head;
  16. ListNode slow = p;
  17. ListNode fast = p;
  18. //先走出几步来;
  19. while(n>0){
  20. fast = fast.next;
  21. n--;
  22. }
  23. //走到终点,走到重点的标志就是最后一个结点;
  24. while(fast.next!=null){
  25. fast = fast.next;
  26. slow = slow.next;
  27. }
  28. //删除
  29. slow.next = slow.next.next;
  30. //因为这个引入的头结点是不会进行任何删除的操作,所以返回他的下一个就行了;
  31. return p.next;
  32. }
  33. }

总结这个题做起来还是比较模糊的,前置一个结点的思想掌握的还是不够好;
image.png

这是最基本的一道链表反转的题考的就是怎么把这个链表反转过来,也就不说什么迭代,递归之类的,我就用我自己的方法,这方法也不是很弱鸡,先看代码:

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode reverseList(ListNode head) {
  13. if( head==null || head.next==null){
  14. return head;
  15. }
  16. ListNode p = head.next;
  17. head.next = null;
  18. while(p!=null){
  19. ListNode v = p.next;
  20. p.next = head;
  21. head = p;
  22. p = v;
  23. }
  24. return head;
  25. }
  26. }

代码解读:首先对于进入的链表进行判断,如果说只有一个结点,或者本身就是空,直接返回就可以了,这里要注意判断的顺序,先判断是不是 null ,再判断有没有next 。然后我们用一个p指针指向当前链表head 的下一个结点,然后因为当前是第一个Node ,所以把他next ,指向空,然后就可以进入循环了,循环结束的标志,那一定是到了链表的尾部,那也就是p!=null,持续循环。在循环体中,我们再用一个指针指向p的next ,这样保证p逆转的时候不会丢失剩下的链表,逆转结束后,我们试想写一部要进行操作的对象中,如今的p就是当年的head ,如今的v就是当年的p,于是交换值,再次循环,知道p是空,代表全部逆转完毕,刚好返回head 就完成了任务;
image.png
简单来看就是归并排序罢了,但是我们没有数组来承接它,所以要改,就把整条链表打乱,定义一个新的头结点,对这两条字符串进行串串,也就是merge过程,这里就不过BB,看代码就好

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
  13. ListNode p = new ListNode();
  14. ListNode p3 = p;
  15. ListNode p1= list1;
  16. ListNode p2= list2;
  17. while(p1!=null&&p2!=null){
  18. if(p1.val<p2.val){
  19. p.next = p1;
  20. p = p.next;
  21. p1 = p1.next;
  22. }
  23. else{
  24. p.next = p2;
  25. p = p.next;
  26. p2 = p2.next;
  27. }
  28. }
  29. while(p1!=null){
  30. p.next = p1;
  31. p = p.next;
  32. p1 = p1.next;
  33. }
  34. while(p2!=null){
  35. p.next = p2;
  36. p = p.next;
  37. p2 = p2.next;
  38. }
  39. return p3.next;
  40. }
  41. }

image.png
这道题的最优解就是利用快慢指针一个走到头,另一个走到中间,然后将中间的结点后面的部分进行逆转,且中间只向空,得到两条链表进行比较得出是否为回文,主要知识点就2个,一个是快慢指针,另一个是反转;

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public boolean isPalindrome(ListNode head) {
  13. //首先定义一个快慢指针,走到最后,快指针指向末尾,满指针直到中间位置;
  14. ListNode fast = head;
  15. ListNode slow = head;
  16. while(fast.next!=null){
  17. fast = fast.next;
  18. if(fast.next == null){
  19. break;
  20. }
  21. fast = fast.next;
  22. slow = slow.next;
  23. }
  24. //这样走完,我们把满指针指向null;
  25. ListNode head2 = reversal(slow.next);
  26. slow.next = null;
  27. while(head!=null && head2!=null){
  28. if(head.val!=head2.val){
  29. return false;
  30. }
  31. head = head.next;
  32. head2 = head2.next;
  33. }
  34. return true;
  35. }
  36. public static ListNode reversal(ListNode head){
  37. if(head==null || head.next== null){
  38. return head;
  39. }
  40. ListNode p = head.next;
  41. head.next = null;
  42. while(p!=null){
  43. ListNode v = p.next;
  44. p.next = head;
  45. head = p;
  46. p = v;
  47. }
  48. return head;
  49. }
  50. }

最后一道,就是扣圈问题,可以走到null,自然不是环,走不到null,迟早会出现二者相等的时刻;

二叉树

  1. package com.zjl;
  2. import java.util.ArrayList;
  3. import java.util.Stack;
  4. /**
  5. * @Author 朱金龙
  6. * @Date
  7. * @Version 1.0
  8. */
  9. public class Binary_tree {
  10. //这里主要要进行总结的知识点一共有3个,分别是 树的非递归三种遍历方式 ,树的广度搜索,以及返回最大的宽度
  11. //这是前序遍历方式
  12. static void PreOrderTraversa(ListNode head){
  13. //前序遍历的方式是中前后,非递归的实现方式:1.head 入栈 ,然后进入循环,循环结束的条件是,栈为空,
  14. // 每次进行的操作就是弹出栈顶,然后压入弹出栈顶的元素的右左结点,如果没有就算了
  15. Stack<ListNode> list = new Stack<>();
  16. list.push(head);
  17. //定义一个cur指针指向我们的head ,后面是会进行动态的变化的
  18. while (!list.empty()){
  19. ListNode cur = list.pop();
  20. System.out.println(cur.value);
  21. if (cur.right!=null){
  22. list.push(cur.right);
  23. }
  24. if (cur.left!=null){
  25. list.push(cur.left);
  26. }
  27. }
  28. }
  29. //后序的遍历方式是前序的改良版, 前序的顺序 是中左右 ,这里我们入栈的顺序是中右左,
  30. // 改成中 左 右 之后 ,1.弹栈的顺序是 中 右 左 2. 另一边入栈的顺序就是 左 右 中
  31. // 放入一个栈中,然后在倒着取出就是后序的顺序了;
  32. static void PostOrderTraversal(ListNode head){
  33. Stack<ListNode> list = new Stack();
  34. Stack<ListNode> res = new Stack();
  35. list.push(head);
  36. while (!list.empty()){
  37. ListNode cur = list.pop();
  38. res.push(cur);
  39. if (cur.left!=null){
  40. list.push(cur.left);
  41. }
  42. if (cur.right!=null){
  43. list.push(cur.right);
  44. }
  45. }
  46. //遍历输出栈
  47. while(!res.empty()){
  48. System.out.println(res.pop().value);
  49. }
  50. }
  51. //中序遍历的方式 , 还是栈,用栈来模拟这个递归
  52. //首先我们 从根结点进行遍历的子节点,入栈,然后进行pop ,pop 的过程中,检查是否有右结点,如果有右结点,
  53. //就以右结点为根,再次进行遍历入栈;
  54. static void InorderTraversal(ListNode head){
  55. Stack<ListNode> list = new Stack<>();
  56. //左结点全部入栈;
  57. while (head!=null){
  58. list.push(head);
  59. head = head.left;
  60. }
  61. while(!list.empty()){
  62. ListNode cur = list.pop();
  63. System.out.println(cur.value);
  64. if (cur.right!=null){
  65. head = cur.right;
  66. //如果不是空的,就怎么处理?
  67. while (head!=null){
  68. list.push(head);
  69. head = head.left;
  70. }
  71. }
  72. }
  73. }
  74. public static void main(String[] args) {
  75. ListNode listNode1 = new ListNode(1);
  76. ListNode listNode2 = new ListNode(2);
  77. ListNode listNode3 = new ListNode(3);
  78. ListNode listNode4 = new ListNode(4);
  79. ListNode listNode5 = new ListNode(5);
  80. ListNode listNode6 = new ListNode(6);
  81. ListNode listNode7 = new ListNode(7);
  82. ListNode listNode8 = new ListNode(8);
  83. listNode1.left = listNode2;
  84. listNode2.left = listNode4;
  85. listNode2.right = listNode5;
  86. listNode1.right = listNode3;
  87. listNode3.left = listNode6;
  88. listNode3.right = listNode7;
  89. listNode7.right = listNode8;
  90. InorderTraversal(listNode1);
  91. // PreOrderTraversa(listNode1);
  92. ArrayList arrayList = new ArrayList();
  93. arrayList.add(0);
  94. }
  95. }
  96. class ListNode {
  97. int value;
  98. ListNode left;
  99. ListNode right;
  100. public ListNode(int value){
  101. this.value = value;
  102. }
  103. }
  1. static void WidthSearch(ListNode root){
  2. //如果要统计最大宽度的话
  3. //统计每一层的数值
  4. //这还能当栈用啊?
  5. ArrayList<List<Integer>> lists = new ArrayList<>();
  6. LinkedList<ListNode> list = new LinkedList<>();
  7. //入队
  8. list.add(root);
  9. //开始进行pop和push操作
  10. while(!list.isEmpty()){
  11. //代表当前层的结点数
  12. int currsize = list.size();
  13. ArrayList<Integer> curvals = new ArrayList<>();
  14. //循环这一层
  15. for (int i = 0; i < currsize; i++) {
  16. //弹出队列
  17. ListNode cur = list.remove();
  18. //可以装进当前这层的集合;
  19. curvals.add(cur.value);
  20. if (cur.left!=null){
  21. list.add(cur.left);
  22. }
  23. if (cur.right!=null){
  24. list.add(cur.right);
  25. }
  26. }
  27. //读完装进大的集合;
  28. lists.add(curvals);
  29. }
  30. }

二叉树的递归

二叉树的递归一共有2种,一种是自上而下的递归,另一种是自下而上的递归方式
两种方式主要不同在于我们看问题的角度不同,
自上而下的方式,是从树的root出发,首先根节点会给子节点一个值,然后子节点接受到值后,会和root结点做同样的操作,把自己的值和root传进来的值分配给左右子结点,知道,左右子结点不能在传递,即到达叶子结点的时候,我们停止递归,然后从下向上一层一层的筛选传递会来的左右结点的值,知道传递到root停止上传。
自下而上的方式,是属于程序从root开始,但是不会传递一个初始值下去,而是开始就进行层层递归,然后等待到达叶子结点后,由叶子结点将数值进行返回,然后将每个叶子结点的返回值进行层层加工,然后筛选出是左还是右边的结点,进行返回。
所以说,往往我们第一种方式的叶子结点返回的值是很大的,是层层积累下来的 ,第二种方式就是较小的,因为每一层的计算方式已经写好了,就等着子节点传回数值进行计算了;
然后,其实两种递归的代码结构都很简单

  1. 1.首先把我们的root结点想象成叶子结点,对它写出临界条件和返回值
  2. 2.无脑的进行递归,然后定义两个变量用来收集我们需要的值
  3. 3.进行上一层的返回,这个返回中要对左右两个返回值进行判断,根据判断结果,来看我们要返回的是哪一个值
  4. 补充:往往复杂的问题,并不会让我们递归到叶子结点才进行返回,往往需要我们分析一下特殊情况
  5. ,当然这些特殊情况直接堆到第一层结构中大多数都是可以解决的;

前缀树

  1. class Trie {
  2. //难道不是只有一个root 就行来了吗;
  3. Node root;
  4. //前缀树的数据结构
  5. class Node{
  6. int pass;//记录走过的次数
  7. int end;//记录多少单词以他结尾
  8. Node[] nodes ;
  9. //26个空间代表着我们的26个可以进行连接的字母;
  10. public Node(){
  11. //在这里进行初始化;
  12. nodes = new Node[26];
  13. }
  14. }
  15. public Trie() {
  16. //怎么进行初始化???
  17. root = new Node();
  18. }
  19. public void insert(String word) {
  20. //传入一个字符串,然后把他放到树中,先转换成字符数组
  21. word.length();
  22. char[] str = word.toCharArray();
  23. // root结点是不会方数字的,但是要一个指针沿着它走下去
  24. Node p = root;
  25. p.pass++;
  26. for (int i = 0; i < str.length; i++) {
  27. //读取数组的每一个字符,然后找对应的数组的位置
  28. int index = str[i] - 'a';
  29. //如果说我们这个node 的位置的这个数组是空的
  30. if (p.nodes[index] == null){
  31. //插入一个新的node
  32. p.nodes[index] = new Node();
  33. }
  34. //如果说已经有了的话
  35. p = p.nodes[index];
  36. p.pass++;
  37. }
  38. p.end++;
  39. }
  40. public boolean search(String word) {
  41. char[] str = word.toCharArray();
  42. //查找操作,肯定是从头查了
  43. Node p = root;
  44. //假设传进来的是空传
  45. //进行查询
  46. for (int i = 0; i < str.length; i++) {
  47. int index = str[i] - 'a';
  48. //先判断是否存在,如果不存在就直接返回false;
  49. if (p.nodes[index] == null){
  50. return false;
  51. }
  52. //如果存在的话,就往下走;
  53. p = p.nodes[index];
  54. }
  55. //遍历完成后,判断一下有没有这个结尾的字符
  56. if (p.end==0){
  57. return false;
  58. }
  59. return true;
  60. }
  61. public boolean startsWith(String prefix) {
  62. //找前缀
  63. char[] str = prefix.toCharArray();
  64. //假设没有insert 代表这个树是完全空的,然后查null
  65. Node p = root;
  66. for (int i = 0; i < str.length; i++) {
  67. int index = str[i] - 'a';
  68. if (p.nodes[index]==null){
  69. return false;
  70. }
  71. p = p.nodes[index];
  72. }
  73. if(p.pass<1){
  74. return false;
  75. }
  76. return true;
  77. }

贪心算法

其实没有什么技巧可以说的,就是对一件事只考虑其中的一个因素罢了