数据结构和算法的部分例题(力扣)

1.数组

1.1 合并一个数组的两个有序区间


public class MargTwo {public static void main(String[] args) {int[] arr1={1,5,6,2,4,10,11};int[] arr2=new int[arr1.length];marg2(arr1,0,2,3,6,arr2);}private static void marg2(int[]arr1,int iStar,int iEnd,int jStar,int jEnd,int []arr2) {int k=0;while (iStar<=iEnd&&jStar<jEnd){if(arr1[iStar]<arr1[jStar]){arr2[k]=arr1[iStar];iStar++;}else{arr2[k]=arr1[jStar];jStar++;}k++;}if(iStar>iEnd){//i区间无元素,复制j区间剩余元素System.arraycopy(arr1,jStar,arr2,k,jEnd-jStar+1);return;}else{//复制i区间剩余元素System.arraycopy(arr1,iStar,arr2,k,iEnd-iStar+1);}}/*** @Description 递归* @Author LY* @Param [arr1, iStar, iEnd, jStar, jEnd, arr2, k]*       arr1: 待合并数组*       iStar:第一个区间开始下标*       iEnd: 一个区间结束下标*       jStar:第二个区间开始下标*       jEnd: 二个区间结束下标*       arr2: 合并后的数组*       k:    新数组下标* @return int[]* @Date 2024/1/17 16:31**/private static void marg1(int[]arr1,int iStar,int iEnd,int jStar,int jEnd,int []arr2,int k){if(iStar>iEnd){//i区间无元素,复制j区间剩余元素System.arraycopy(arr1,jStar,arr2,k,jEnd-jStar+1);return;}else{//复制i区间剩余元素System.arraycopy(arr1,iStar,arr2,k,iEnd-iStar+1);}if(arr1[iStar]<arr1[jStar]){arr2[k]=arr1[iStar];iStar++;}else{arr2[k]=arr1[jStar];jStar++;}marg1(arr1,iStar,iEnd,jStar,jEnd,arr2,k+1);}
}

2.链表 

2.1 反转单向链表

(方法1)构建一个新的链表,从就链表依次拿到每个节点,创建新的节点添加至新链表头部,完成后的新链表就是倒序的,简单,但是需要创建新的对象

//从头部取值插入尾部private static ListNode firstToLast(ListNode listNode) {//创建新的链表从第一个元素开始向前插入ListNode head = null;//指针指向传入元素ListNode p = listNode;while (p != null) {//原本的头部变成p.next之后的val元素,head = new ListNode(p.val, head);p = p.next;}return head;}

(方法2)与方法1类似,构建新的链表,从头部移除节点,添加至新链表头部,完成后新链表既是倒序的,需要构建一个容器类,但是不去创建新的节点,更加偏面向对象

//容器类static class MyList {ListNode head;public MyList(ListNode head) {this.head = head;}public ListNode removeFirst() {ListNode first = head;if (first != null) {head = first.next;}return first;}public void addFirst(ListNode first) {first.next = head;head = first;}}//从头部移除元素插入新链表尾部private static MyList moveFirst(ListNode listNode) {MyList oMyList = new MyList(listNode);MyList nMyList = new MyList(null);while (oMyList.head != null) {nMyList.addFirst(oMyList.removeFirst());}return nMyList;}

(方法3)利用递归特性,让最后一个节点指向他的上一个节点,上一个节点指向null

  //递归调用,最后一个节点private static ListNode adjoin(ListNode listNode) {//链表为空或者下一个节点为空直接返回if (listNode == null || listNode.next == null) {return listNode;}//否则向下寻找ListNode last = adjoin(listNode.next);//让当前节点的下一个节点的下一个节点指向当前节点listNode.next.next = listNode;//当前节点下一个节点指向空listNode.next = null;return last;}

(方法4)每次都取第二个节点插入到头部,直到第二个节点为null,但是要考虑链表只有一个元素或者为null的特殊情况

/** 设置两个指针,old1,old2和new old1,new1都指向第一个节点,old2指向就链表的第二个元素* 不断地将old2插入到new的前方,old.next==null结束调用* */private static ListNode oldToNew(ListNode old1) {//处理空链表和一个元素if(old1==null||old1.next==null){return old1;}//第二个节点ListNode old2 = old1.next;//新的链表ListNode new1 = old1;//old为空终止循环while (old2 != null) {//断开old2old1.next = old2.next;//把old2.next指向new1old2.next = new1;//new1重新赋值new1=old2;//old2指向下一个节点old2=old1.next;}return new1;}

(方法5)类似于方法2,但是不用创建新的容器,偏向于面向过程

/**  类似于firstToLast,不用MyList* */private static ListNode firstToLast2(ListNode old1) {//处理空链表和一个元素if(old1==null||old1.next==null){return old1;}//新链表ListNode new1=null;while (old1!=null){ListNode old2=old1.next;old1.next=new1;//old1为需要的节点new1=old1;//old2为断开第二个节点后的链表old1=old2;}return new1;}

2.2 根据值删除节点


public class DeleteByVal {public static void main(String[] args) {ListNode listNode1 = new ListNode(1, new ListNode(2, new ListNode(3, null)));ListNode listNode2 = new ListNode(1, new ListNode(2, new ListNode(2, null)));ListNode listNode3 = new ListNode(2, new ListNode(2, new ListNode(2, null)));ListNode remove1 = remove(listNode1, 2);ListNode remove2 = remove(listNode2, 2);ListNode remove3 = remove(listNode3, 2);ListNode remove4 = remove2(listNode1, 2);ListNode remove5 = remove2(listNode2, 2);ListNode remove6 = remove2(listNode3, 2);}public static ListNode remove2(ListNode p, int val) {if (p == null) {return null;}if (p.val == val) {return remove2(p.next, val);}else{p.next= remove2(p.next, val);return p;}}public static ListNode remove(ListNode head, int val) {//创建哨兵ListNode s = new ListNode(-1, head);ListNode piont1 = s;ListNode piont2 = s.next;while (piont2 != null) {if (val == piont2.val) {//删除只移动p2piont1.next = piont2.next;piont2 = piont1.next;} else {//不删除移动p1,p2piont1 = piont2;piont2 = piont2.next;}}return s.next;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.3 删除倒数节点


public class DeleteTailend {public static void main(String[] args) {ListNode listNode = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, null)))));
//        listNode=remove(listNode, 5);listNode=remove2(listNode, 5);}//快慢指针法public static ListNode remove2(ListNode listNode, int n) {ListNode s=new ListNode(-1,listNode);ListNode p1=s;ListNode p2=s;for (int i = 0; i < n+1; i++) {p2=p2.next;}while(p2!=null){p1=p1.next;p2=p2.next;}p1.next=p1.next.next;return s.next ;}//删除倒数第n个接点 递归public static ListNode remove(ListNode listNode, int n) {ListNode s=new ListNode(-1,listNode);recurs( s, n);return s.next ;}private static int recurs(ListNode p,int n){if (p == null) {return 0;}int nth= recurs(p.next, n);if(nth==n){p.next=p.next.next;}return nth+1;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.4 有序链表重复节点(保留1个)


public class DeleteRepetition {public static void main(String[] args) {ListNode listNode = new ListNode(1, new ListNode(3, new ListNode(3, new ListNode(3, new ListNode(3, null)))));ListNode remove = remove2(listNode);}public static ListNode remove2(ListNode listNode){if(listNode==null|| listNode.next==null){return listNode;}if(listNode.val==listNode.next.val){//重复 则不要当前节点return remove2(listNode.next);}else{//下一个节点要更新为.next去重后的链表listNode.next=remove2(listNode.next);return listNode;}}public static ListNode remove(ListNode listNode){if(listNode==null|| listNode.next==null){return listNode;}ListNode p1=listNode;ListNode p2=listNode.next;while (p2!=null){if(p1.val==p2.val){p1.next=p2.next;}else{p1=p2;}p2=p1.next;}return listNode;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.5 有序链表重复节点(全部删除)


public class DeleteRepetition2 {public static void main(String[] args) {ListNode listNode = new ListNode(1, new ListNode(2, new ListNode(2, new ListNode(3, new ListNode(3, null)))));ListNode remove = remove2(listNode);}//快慢指针public static ListNode remove2(ListNode listNode){if(listNode==null|| listNode.next==null){return listNode;}ListNode s=new ListNode(-1,listNode);ListNode pinot1=s;ListNode pinot2,pinot3;while ((pinot2=pinot1.next)!=null&& (pinot3=pinot2.next)!=null){if(pinot3!=null&&pinot2.val==pinot3.val){while ((pinot3=pinot3.next)!=null&&pinot3.val==pinot2.val){}pinot1.next=pinot3;}else{pinot1=pinot1.next;}}return listNode;}public static ListNode remove(ListNode listNode){if(listNode==null|| listNode.next==null){return listNode;}//前后值一样if(listNode.val==listNode.next.val){//去寻找下一个直到与当前节点不同ListNode x=listNode.next.next;while (x!=null && x.val==listNode.next.val){//返回他的下一个节点x=x.next;}return remove(x);}else {//更新next后的节点也需要去重listNode.next=remove(listNode.next);return listNode;}}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.6 合并有序链表


public class MergeTwo {public static void main(String[] args) {/*  ListNode listNode1 = new ListNode(1, new ListNode(2, new ListNode(2, null)));ListNode listNode2 = new ListNode(3, new ListNode(3, new ListNode(4, null)));ListNode marge = marge(listNode1, listNode2);ListNode listNode3 = new ListNode(1, new ListNode(3, new ListNode(5, null)));ListNode listNode4 = new ListNode(2, new ListNode(4, new ListNode(6, null)));ListNode marge2 = marge(listNode1, listNode2);*//* ListNode listNode1 = new ListNode(1, new ListNode(2, new ListNode(2, null)));ListNode listNode2 = new ListNode(3, new ListNode(3, new ListNode(4, null)));ListNode marge = marge2(listNode1, listNode2);*/ListNode listNode3 = new ListNode(1, new ListNode(3, new ListNode(5, null)));ListNode listNode4 = new ListNode(2, new ListNode(4, new ListNode(6, null)));ListNode marge2 = marge2(listNode3, listNode4);}//递归public static ListNode marge2(ListNode listNode1, ListNode listNode2) {if (listNode1 == null) {return listNode2;}if (listNode2 == null) {return listNode1;}if (listNode1.val < listNode2.val) {listNode1.next = marge2(listNode1.next, listNode2);return listNode1;} else {listNode2.next = marge2(listNode1, listNode2.next);return listNode2;}}//循环处理public static ListNode marge(ListNode listNode1, ListNode listNode2) {ListNode s = new ListNode(-1, null);ListNode p = s;while (listNode1 != null && listNode2 != null) {//把小的值插入到链表后方if (listNode1.val > listNode2.val) {//listNode2小,插入listNode2的值到新的链表,并将指针后移p.next = listNode2;listNode2 = listNode2.next;} else if (listNode1.val < listNode2.val) {//listNode1小,插入listNode1的值到新的链表,并将指针后移p.next = listNode1;listNode1 = listNode1.next;} else {p.next = listNode1;listNode1 = listNode1.next;p = p.next;p.next = listNode2;listNode2 = listNode2.next;}//将新链表指针移动到最后p = p.next;}//生效的全部都是大的if (listNode1 != null) {p.next = listNode1;}if (listNode2 != null) {p.next = listNode2;}return s.next;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.7 合并多个有序链表

public class MergeMany {public static void main(String[] args) {ListNode listNode1 = new ListNode(1, new ListNode(4, new ListNode(7, null)));ListNode listNode2 = new ListNode(1, new ListNode(5, new ListNode(8, null)));ListNode listNode3 = new ListNode(3, new ListNode(6, new ListNode(9, null)));ListNode []list ={listNode1,listNode2, listNode3};ListNode spilt = spilt(list, 0, list.length - 1);}/*** @Description 返回合并后的数组* @Author LY* @Param [list, i, j]  list 数组 , i左边界,j右边界* @Date 2024/1/17 10:05**/private static ListNode spilt(ListNode[] list,int i,int j){//结束条件 数组内只有一个链表if(i==j){return  list[i];}int m = i+j >>>1;//取中间//从0到中间ListNode left = spilt(list, 0, m);//从中间+1到结束ListNode right = spilt(list, m + 1, j);return marge(left,right);}//循环处理合并两个链表public static ListNode marge(ListNode listNode1, ListNode listNode2) {ListNode s = new ListNode(-1, null);ListNode p = s;while (listNode1 != null && listNode2 != null) {//把小的值插入到链表后方if (listNode1.val > listNode2.val) {//listNode2小,插入listNode2的值到新的链表,并将指针后移p.next = listNode2;listNode2 = listNode2.next;} else if (listNode1.val < listNode2.val) {//listNode1小,插入listNode1的值到新的链表,并将指针后移p.next = listNode1;listNode1 = listNode1.next;} else {p.next = listNode1;listNode1 = listNode1.next;p = p.next;p.next = listNode2;listNode2 = listNode2.next;}//将新链表指针移动到最后p = p.next;}//生效的全部都是大的if (listNode1 != null) {p.next = listNode1;}if (listNode2 != null) {p.next = listNode2;}return s.next;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.8 查找中间节点


public class FindMiddle {public static void main(String[] args) {ListNode listNode1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6, new ListNode(7, null)))))));ListNode listNode2 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5, new ListNode(6, null))))));ListNode middle1 = findMiddle1(listNode1);ListNode middle2 = findMiddle1(listNode2);}/** 快慢指针* 快指针一次走两个,慢指针一次走一个,* 当快指针走到结束,慢指针则在链表中间元素*** */private static ListNode findMiddle1(ListNode head){ListNode quick=head;ListNode slow=head;while (slow!=null&&slow.next!=null){slow =slow.next;quick=quick.next.next;}return slow;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.9 判断回文链表


public class IsPalindrome {public static void main(String[] args) {ListNode listNode1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(3, new ListNode(2, new ListNode(1,null))))));ListNode listNode2 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(2, new ListNode(1,null)))));ListNode listNode3 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(3, new ListNode(1,null)))));Boolean palindrome1 = isPalindrome(listNode1);Boolean palindrome2 = isPalindrome(listNode2);Boolean palindrome3 = isPalindrome(listNode3);}/**   优化,合并代码** */public static Boolean isPalindrome(ListNode head){/*//查找中间元素ListNode quick=head;ListNode slow=head;while (quick!=null&&quick.next!=null){slow =slow.next;quick=quick.next.next;}//反转链表ListNode n1=null;while (slow!=null){//旧节点的下一个ListNode old2=slow.next;//旧链表第一个节点和新链表链接slow.next=n1;//新链表为旧链表和新链表链接后的结果n1=slow;//旧链表等于他的下一个元素slow=old2;}*///查找和反转合并ListNode quick=head;//快指针ListNode slow=head;//慢指针ListNode old1=head;//旧的头部ListNode new1=null;//新的头部while (quick!=null&&quick.next!=null){slow =slow.next;quick=quick.next.next;//反转逻辑//旧节点的下一个ListNode  old2=old1.next;old1.next=new1;new1=old1;old1=old2;}while (new1.next!=null){if(quick!=null){//奇数节点slow=slow.next;}if(new1.val!=slow.val){return false;}new1=new1.next;slow=slow.next;}return true;}/** 1.查找到中间节点,* 2.向后的元素全部反转,* 3.然后从头开始判断,完全相同则为回文链表* */public static Boolean isPalindrome2(ListNode head){//查找中间元素ListNode middle1 = findMiddle1(head);//反转链表ListNode reverse = reverse(middle1);while (reverse.next!=null){if(reverse.val!=head.val){return false;}reverse=reverse.next;head=head.next;}return true;}private static ListNode reverse(ListNode old1){ListNode n1=null;while (old1!=null){//旧节点的下一个ListNode old2=old1.next;//旧链表第一个节点和新链表链接old1.next=n1;//新链表为旧链表和新链表链接后的结果n1=old1;//旧链表等于他的下一个元素old1=old2;}return n1;}/** 快慢指针* 快指针一次走两个,慢指针一次走一个,* 当快指针走到结束,慢指针则在链表中间元素*** */private static ListNode findMiddle1(ListNode head){ListNode quick=head;ListNode slow=head;while (quick!=null&&quick.next!=null){slow =slow.next;quick=quick.next.next;}return slow;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

2.10 判环算法

     

        判环算法通常是指用于检测在链表或数组等数据结构中是否存在环的一种方法。这里以链表中的判环算法为例,其中最经典的实现是Floyd判环算法(也称作龟兔赛跑算法)。

Floyd判环算法详解:

  1. 基本思想:

    • 假设有两个指针,一个快指针(比如兔子)每次移动两个节点,另一个慢指针(比如乌龟)每次移动一个节点。
    • 如果链表中不存在环,因为快指针速度更快,它最终会到达链表的尾部而结束遍历,此时可以确定无环。
    • 若链表中存在环,则快指针会在某个时刻追上慢指针,因为在环内它们一直在循环,并且由于速度差的关系,必然会在环内的某一点相遇。
  2. 算法步骤:

    • 初始化两个指针fastslow,都指向链表头结点。
    • fastslow都不为空时,持续进行以下操作:
      • fast指针向前移动两个节点(fast = fast.next.next)。
      • slow指针向前移动一个节点(slow = slow.next)。
    • 检查fastslow是否指向同一个节点。如果是,则说明链表中有环;否则,若fast达到链表末尾(即fast.next == null),则说明链表无环。
  3. 计算环长度(可选):

    • 在判断出有环之后,为了找到环的长度,可以让其中一个指针(如slow)保持不动,另一个指针(fast)重置到头结点并每次只前进一个节点。
    • 这样当fast再次与slow相遇时,fast所走过的步数除以2就是环的长度(因为之前相遇时,fast走了slow两倍的距离)。
  4. 时间复杂度和空间复杂度:

    • 时间复杂度:O(n),其中n为链表的长度。最坏情况下,两个指针都需要遍历整个链表一次才能确定是否有环。
    • 空间复杂度:O(1),因为只需要常数级别的额外空间来存储两个指针。

       通过这种巧妙的方法,Floyd判环算法可以在不增加额外空间复杂度的情况下高效地检测链表中是否存在环。

       假设乌龟和兔子相遇在环中的某一点P,由于兔子每次移动两个节点,而乌龟每次移动一个节点,在它们相遇时,兔子相对于乌龟多走了一倍的距离。这一倍的距离实际上就是从头结点开始到环入口点的链长,因为在这段距离里,兔子比乌龟每轮循环都多前进一个节点,所以最终能在环内相遇。

       首先,兔子和乌龟从同一个起点开始,兔子每次移动两个节点,乌龟每次移动一个节点。当它们在环内相遇时,假设兔子走了m步(经过了m个节点),乌龟走了n步(经过了n个节点),且m = 2n(因为兔子速度是乌龟的两倍)。

此时,兔子相对于乌龟多走了一倍的距离,这一倍的距离实际上是从链表头部到环入口点的距离,记作d。

       接下来为了计算环的大小(即环中的节点数),我们可以让乌龟保持不动,而将兔子重置回头结点,然后两者都以乌龟的速度(一次一格)前进。再次相遇时,兔子又走过了d距离加上环的一个完整周长c。

所以有:兔子第二次走过的总距离 = d + c。

       由于兔子第二次走的速度与乌龟相同,乌龟也同步前进了c距离(环的周长)。因此:

兔子走的距离 - 乌龟走的距离 = (d + c) - c = d

       这里d就是从链表头到环入口的距离。

在Floyd判环算法中,d是从链表头到环入口的距离的原因可以这样解释:

  1. 初始时,兔子(快指针)和乌龟(慢指针)都从链表的头结点开始移动。

  2. 兔子每次移动两个节点,乌龟每次移动一个节点。当链表存在环时,因为兔子的速度是乌龟的两倍,在环内兔子最终会追上乌龟,即两者在环中的某一点相遇。

  3. 从开始至相遇过程中,兔子走过的路径包含了从链表头到环入口的这段距离d(因为兔子先到达了环),以及环内的一部分或几圈。而乌龟走过的路径只包含了从链表头到环入口的这段距离d以及与乌龟相遇位置相对应的环内部分。

  4. 当兔子追上乌龟时,由于兔子的速度更快,它在环内的多走的部分刚好等于乌龟还未进入环前的那段距离d。也就是说,兔子比乌龟多走了一个从头结点到环入口的距离。

因此,我们说d是从链表头到环入口的距离。如果要计算环的大小,可以通过让其中一个指针停留在相遇点,另一个指针重置回头结点,然后两个指针以相同速度前进再次相遇的方式来实现,此时两者共同走过的额外距离就是环的长度。


public class IsAnnulus {public static void main(String[] args) {ListNode listNode=new ListNode(-1,null);ListNode listNode1 = new ListNode(1,null);ListNode listNode2 = new ListNode(2,null);ListNode listNode3 = new ListNode(3,null);ListNode listNode4 = new ListNode(4,null);ListNode listNode5 = new ListNode(5,null);ListNode listNode6 = new ListNode(6,null);listNode.next=listNode4;listNode1.next=listNode2;listNode2.next=listNode3;listNode3.next=listNode;ListNode aunnulus1 = isAunnulus(listNode1);listNode4.next=listNode5;listNode5.next=listNode6;listNode6.next=listNode;//第一次相遇ListNode aunnulus2 = isAunnulus(listNode1);//第二次相遇,环的入口ListNode aunnulus3 = findEntrance(listNode1);}//确认是否有环private static ListNode findEntrance(ListNode head){ListNode quick=head;ListNode slow=head;if(head==null){return null;}while (quick!=null&&quick.next!=null){slow=slow.next;quick=quick.next.next;if(slow==quick){slow=head;while (true){//如果quick等于head,说明首尾首相连if(slow==quick){return slow;}slow=slow.next;quick=quick.next;if(quick==slow){return slow;}}}}return null;}//确认是否有环private static ListNode isAunnulus(ListNode head){ListNode quick=head;ListNode slow=head;if(head==null){return null;}while (quick!=null&&quick.next!=null){slow=slow.next;quick=quick.next.next;if(slow==quick){return slow;}}return null;}static class ListNode {int val;ListNode next;ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}

3.递归(补充)

递归定义:

        递归通常用于解决问题的一种方法,其中一个问题的解决方案可以通过解决其更小的同类问题来构建。这里的“同一类概念的更小子集”是指将原问题分解为一个或多个与原问题相似但规模更小的子问题

        (1)自己调用自己(有规律的)。

        (2)每次调用,函数处理的数据规模,会较上次缩减(子集),而后许逐步缩减到无需递归。

        (3)内层函数调用(子集处理)完成,外层函数才能算调用完成。

  //调用递归public void recursionLoop(){this.recursion(sentinel.next);}//递归遍历private void recursion(Node curr){if(curr !=sentinel){System.out.println("befor"+curr.value);recursion(curr.next);System.out.println("after"+curr.value);}}
//befor1,befor4,befor5,befor6,befor7
//after7,after6,after5,after4,after1

思路:

        能否使用递归求解。

        推导出地推关系:父问题与子问题之间的关系,以及递归结束的条件。

例如之前遍历的链表递推关系为:

f(n)停止curr==sentinel
f(n.next)curr!=sentinel

深入到最里层叫做:递。

从最里层出来叫做:归。

在递的过程中,外层函数内的局部变量(及方法参数并未消失),归的时候还可以用到。

 3.1 N的阶乘

递推关系:

f(n)停止n==1
n*f(n-1)n>1
  public static void main(String[] args) {System.out.println(factorialN(10));}public static int factorialN(int n){if(n==1){return 1;}return n*factorialN(n-1);}

3.2 反向打印字符串

递推关系:

f(n)停止n==str.length
f(n+1)0<=n<=str.length
public static void main(String[] args) {reverseStr(0,"abcdefg");}//反向打印字符串public static void reverseStr(int n,String str){if(n==str.length()){return;}reverseStr(n+1,str);System.out.println(str.charAt(n));}

 3.3递归二分查找

 public static void main(String[] args) {int [] arr={1,2,3,4,5,6,7,8,9,10};System.out.println( recursionBinarySearch(arr,5));System.out.println( recursionBinarySearch(arr,1));System.out.println( recursionBinarySearch(arr,10));System.out.println( recursionBinarySearch(arr,11));}public static int recursionBinarySearch(int []arr,int target){return recursionBinarySearch(arr,0,arr.length-1,target);}//递归二分查找private static int  recursionBinarySearch(int []arr,int i,int j,int target){if(i>j)  {return -1;}int m =(i+j)>>>1;if(target<arr[m]){return recursionBinarySearch(arr,i,m-1,target);}else if(arr[m]<target){return  recursionBinarySearch(arr,m+1,j,target);}else {return m;}}

3.4 递归冒泡排序

递推关系:

f(n)停止j==0
f(j-1)arr.length-1>j>0
 public static void main(String[] args) {System.out.print("排序前==》");int[] randomArr = getRandomArr();bubbleSort(randomArr);System.out.println();System.out.print("排序后==》");for (int num : randomArr) {System.out.print(num + ",");}}//递归实现冒泡排序public static void bubbleSort(int [] arr){bubbleSort( arr,arr.length-1);}/*** @Description* @Author LY* @Param [arr, j] arr:需要排序数组,j需要排序的右边界* @return void* @Date 2023/12/18 15:29**/private static void bubbleSort(int arr[],int j){if(j==0){return;}for (int i = 0; i < j; i++) {if(arr[i]>arr[i+1]){int temp=  arr[i];arr[i]=arr[i+1];arr[i+1]=temp;}}bubbleSort(arr,j-1);}//获取一个随机数组public static  int[] getRandomArr(){int n = 10; // 随机数组的长度int lowerBound = 0; // 数组元素的最小值int upperBound = 50; // 数组元素的最大值Random random = new Random();int[] randomArray = new int[n];for (int i = 0; i < n; i++) {randomArray[i] = random.nextInt(upperBound - lowerBound + 1) + lowerBound;}for (int num : randomArray) {System.out.print(num + ",");}return randomArray;}

3.4 递归冒泡排序-改进

有些排序后,已经是有序地元素排列,下次比较无需重新对比,如果使用j,每次递减1,会导致效率下降,可以定义一个x,当i发生交换,x=1;不发生交换说明后续元素都是顺序排列了。

 private static void bubbleSort2(int arr[],int j){if(j==0){return;}int x=0;for (int i = 0; i < j; i++) {if(arr[i]>arr[i+1]){int temp=  arr[i];arr[i]=arr[i+1];arr[i+1]=temp;x=i;}}bubbleSort2(arr,x);}

3.5插入排序

递推关系:

f(n)停止n==arr.length
f(arr,low)0<n<arr.length
public static void main(String[] args) {System.out.print("排序前==》");int[] randomArr = getRandomArr();insertionSort(randomArr);System.out.println();System.out.print("排序后==》");for (int num : randomArr) {System.out.print(num + ",");}}//递归实现插入排序public static void insertionSort(int [] arr){insertionSort(arr,1);}/*** @Description* @Author LY* @Param [arr, low] arr:需要排序的数组,low 未排序区域的左边界* @return void* @Date 2023/12/20 13:50**/public static void insertionSort(int [] arr,int low){if(low==arr.length){return;}//存储low指针指向元素int temp=arr[low];int i=low-1;//i是已排序区域的右边界while (i>=0&&arr[i]>temp){arr[i+1]=arr[i];//空出插入位置i--;//继续向左}//找到插入位置if(i+1!=low){arr[i+1]=temp;}insertionSort(arr,low+1);}

插入排序是将插入元素与前一个元素逐个比较,直到找到arr[i-1]<temp<arr[i],temp为要插入的位置,如果不满足,则逐个后移。

3.6 多路递归-斐波那契

之前的例子,每个递归函数只包含一个自身的调用,这称之为:single recursion。

如果每个递归函数包含多个自身调用则称之为:multi recursion。

递推关系:

f(n)0n=0
1n=1
f(n-1)+f(n-2)n>1
public static void main(String[] args) {int fibonacci = fibonacci(8);System.out.println("斐波那契数列求和:"+fibonacci);}public static int  fibonacci(int n){if(n<=1){return n;}else {return fibonacci(n - 1)+fibonacci(n - 2);}}//获取1-100随机正整数public static int getRandom(){Random rand = new Random();int randomNum = rand.nextInt(100) + 1;System.out.print("取得随机数:"+randomNum);return randomNum;}

(1)递归次数也符合斐波那契规律:2*f*(n+1)-1。

(2)时间复杂度推导过程:

        

3.6.1 斐波那契-兔子问题

第一个月有一对未成熟的兔子,第二个月他们成熟,第三个月他们能产下一对新的小兔子,求第n个月的兔子数。

    public static int fibonacciRabbit(int n){if(n<=2){return 1;}return fibonacciRabbit(n-1)+fibonacciRabbit(n-2);}
 3.6.2 斐波那契-青蛙问题
青蛙一次可以跳一个台阶,或者两个台阶,跳n阶一共有几种跳法。
​    public static int fibonaccifrog(int n) {if(n<=2){return n;}return fibonaccifrog(n-1)+fibonaccifrog(n-2);}​
 3.6.3斐波那契-优化-缓存

        fibonacci方法是一个未优化的递归实现,每次计算斐波那契数时都会递归调用自身两次,这导致了大量的重复计算。其时间复杂度为O(2^n)。
        fibonacciOptimize方法是一个优化过的版本,它使用了动态规划的思想,通过一个缓存数组cache来存储已经计算过的斐波那契数。在计算某个斐波那契数时,首先检查它是否已经在缓存中,如果在,则直接返回缓存的值;如果不在,则计算后将结果存入缓存,然后再返回。这样就避免了重复计算,大大提高了效率。其时间复杂度为O(n)。

随着数据规模的增加,两者运行时间差异就会越来越大。

  public static void main(String[] args) {int n=3;long OptimizeTime=0;long fibonacciTime=0;int ite=10;for (int i = 0; i < ite; i++) {long fibonacciStarOptimize = System.currentTimeMillis();int fibonacciOptimize = fibonacciOptimize(n);System.out.println("fibonacciOptimize:第"+i+"次结果:"+fibonacciOptimize);long fibonacciEndOptimize = System.currentTimeMillis();OptimizeTime+=(fibonacciEndOptimize-fibonacciStarOptimize);}for (int i = 0; i < ite; i++) {long fibonacciStar = System.currentTimeMillis();int fibonacci = fibonacci(n);System.out.println("fibonacci:第"+i+"次结果:"+fibonacci);long fibonacciEnd = System.currentTimeMillis();fibonacciTime+=(fibonacciEnd-fibonacciStar);}System.out.println("OptimizeTime平均执行时间为:"+(OptimizeTime/ite));System.out.println("fibonacci平均执行时间为:"+(fibonacciTime/ite));}public static int  fibonacciOptimize(int n) {int[] cache=new int[n+1];//初始化数组内容为-1Arrays.fill(cache,-1);//初始化第一个和第二个元素cache[0]=0;cache[1]=1;return fibonacci2(n,cache);}//斐波那契数列优化public static int  fibonacci2(int n,int[]cache){if(cache[n]==-1){cache[n]= fibonacci2(n - 1,cache)+fibonacci2(n - 2,cache);}return cache[n];}//斐波那契数列public static int  fibonacci(int n){if(n<=1){return n;}else {return fibonacci(n - 1)+fibonacci(n - 2);}}

 3.7 递归-爆栈问题

        爆栈问题通常发生在编程中使用递归调用时,特别是在没有适当优化或者处理深度较大的递归情况下。当递归调用的层次过深,超过了系统为函数调用分配的栈空间(Stack)的大小限制时,就会发生栈溢出(Stack Overflow),也就是所谓的“爆栈”。

        在未优化的斐波那契数列计算中,如原始的fibonacci方法,每次递归都会产生两个新的递归调用,导致调用栈的增长非常快。对于较大的n值,这将迅速耗尽栈空间,从而导致爆栈。

解决方案:

为了避免爆栈问题,可以采取以下几种方法:

        使用迭代代替递归:将递归算法转换为循环迭代算法,这样就可以避免栈空间的持续增长。
        使用尾递归优化:某些编程语言和编译器支持尾递归优化。在尾递归优化中,如果递归调用是函数体中的最后一句,并且返回值直接是该递归调用的结果,编译器或解释器可以避免创建新的栈帧,从而减少栈空间的使用。然而,Java目前并不原生支持尾递归优化。
        使用记忆化技术(如在这个例子中的fibonacciOptimize方法):通过存储已经计算过的中间结果,避免重复计算,从而减少递归调用的深度和次数。
        增大栈空间大小:虽然这不是一个根本的解决办法,但在某些情况下,可以通过设置程序或环境变量来增大栈空间的大小,以防止爆栈。但这只是临时解决方案,对于非常大的输入值,最终仍然可能遇到栈溢出问题。

3.7.1 尾调用

        如果一个函数的最后一个是调用一个函数那么可以称之为尾调用,一些编译器会自动优化尾调用:

function a(){return b()
}

以下下三段代码不能算作尾调用:

(1)最后一步并非调用函数

function a(){const c=b();return c;
}

(2)最后一步虽然调用了函数,但是调用结束又用到了外层函数的值1

function a(){return b()+1;
}

(3)最后一步虽然调用了函数,但是调用结束又用到了外层函数的值x

function a(x){return b()+x;
}

会优化的部分语言:

(1)JavaScript (ES6 及以上):在严格模式下,JavaScript 引擎实现了尾调用优化。这使得在满足特定条件的尾递归调用中,引擎可以避免创建新的栈帧,从而防止栈溢出。
(3)Lua:Lua 语言支持尾调用优化,它能够识别并优化尾递归调用,以减少栈空间的使用。
(4)Scheme 和 Racket:作为 Lisp 家族的一员,Scheme 和 Racket 语言强制要求实现尾调用优化,这是它们语言规范的一部分。
(5)Haskell:虽然 Haskell 不是通过传统的函数调用栈来处理函数调用,但它的惰性求值和无环数据类型结构在某种程度上提供了类似于尾调用优化的效果。
(6)Erlang:Erlang 语言对尾调用进行了优化,因为其设计目标之一就是支持高并发和容错的系统,其中尾调用优化对于避免栈溢出至

关重要。
(7)OCaml 和 F#:这些函数式编程语言也支持尾调用优化。

 3.7.2 尾递归

尾递归是一种特殊的尾调用。优化之后,当他调用内层函数时,会释放外层函数内存。

3.7.3 循环解决

解决递归爆栈问题,根本的解决方案是不适用递归,所有递归都可以改成循环(有些递归修改为循环比较困难):

public static void main(String[] args) {long sum = 0;
//        sum=sum(10500);for (int i = 0; i < 50000; i++) {sum+=i;}System.out.println("求和值:"+sum);}

3.8计算递归时间复杂度-定理求解

3.8.1 例题

 3.8.2 二分查找-递归版

 3.8.3 归并排序-递归版

3.8.4 快速排序-分好

3.9计算递归时间复杂度-展开求解

3.9.1 递归求和

3.9.2 递归冒泡排序

3.9.3 快排-未分好

3.9.4 汉诺塔

public class RecursionProblemMain {public static void main(String[] args) {initTowerOfHanoi(3);move(3,a,b,c);}static LinkedList<Integer> a=new LinkedList<>();static LinkedList<Integer> b=new LinkedList<>();static LinkedList<Integer> c=new LinkedList<>();//汉诺塔public static void towerOfHanoi(int n){initTowerOfHanoi( n);print();b.addLast(a.removeLast());print();}/*** @Description* @Author LY* @Param [n, a, b, c]n:圆盘个数,a:原本位置,b:借助位置,c:目标位置* @return void* @Date 2023/12/22 16:04**/static void move(int n,LinkedList<Integer>a,LinkedList<Integer>b,LinkedList<Integer>c){if(n==0){return;}move(n-1,a,c,b);//最后一个盘子从a移动到cc.addLast(a.removeLast());print();move(n-1,b,a,c);}static void print(){System.out.println(a);System.out.println(b);System.out.println(c);System.out.println("-------------");}static void initTowerOfHanoi(int n){for (int i = n; i >0; i--) {a.addLast(i);}}//递归求和public static long sum(long n) {if (n == 1) {return 1;}return sum(n - 1) + n;}}
3.9.5 杨辉三角

public class Triangle {public static void main(String[] args) {//Triangle1.print(6);
//        Triangle2.print(6);Triangle3.print(6);}//一维数组优化 优化二维数组的空间static class Triangle3 {//数组每一列的每个位置的元素都等于上一行row[j]+row[j-1]private static void createRow(int [] row,int i){if(i==0){row[0]=1;return;}for (int j = i; j >0; j--) {row[j]=row[j]+row[j-1];}}private static void printSpacing(int n, int i) {int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}//打印创建好的数组的相应位置的元素private static void print(int n) {int []  row=new int[n];for (int i = 0; i < n; i++) {createRow(row,i);printSpacing(n, i);for (int j = 0; j <= i; j++) {System.out.printf("%-4d", row[j]);}System.out.println();}}}//二维数组缓存优化 优化时间static class Triangle2 {private static void printSpacing(int n, int i) {int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}//将查询过得值存到二维数组中,如果查询过直接返回private static void print(int n) {int [] [] cache=new int[n][];for (int i = 0; i < n; i++) {cache[i]=new int[i+1];printSpacing(n, i);for (int j = 0; j <= i; j++) {System.out.printf("%-4d", element(cache,i, j));}System.out.println();}}private static int element(int [][] cache,int i, int j) {if(cache[i][j]>0){return cache[i][j];}if (j == 0 || i == j) {cache[i][j]=1;return 1;}cache[i][j]= element(cache,i - 1, j - 1) + element(cache,i - 1, j);return cache[i][j];}}//递归实现static class Triangle1 {private static void printSpacing(int n, int i) {//输出空格个数int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}private static void print(int n) {for (int i = 0; i < n; i++) {printSpacing(n, i);for (int j = 0; j <= i; j++) {System.out.printf("%-4d", element(i, j));}System.out.println();}}//获取元素private static int element(int i, int j) {if (j == 0 || i == j) {return 1;}return element(i - 1, j - 1) + element(i - 1, j);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式-Stm32-江科大基于标准库的GPIO4个小实验

文章目录 一 、硬件介绍二 、实验&#xff1a;LED闪烁、LED流水灯、蜂鸣器提示2.1 需求1&#xff1a;面包板上的LED以1s为周期进行闪烁。亮0.5s,灭0.5s.....2.2 需求2: 8个LED实现流水灯 三、硬件介绍-按键开关、光敏电阻四、 实验 按键控制LED、光敏传感器控制蜂鸣器4.1 需求1…

【MYSQL】存储引擎MyISAM和InnoDB

MYSQL 存储引擎 查看MySQL提供所有的存储引擎 mysql> show engines; mysql常用引擎包括&#xff1a;MYISAM、Innodb、Memory、MERGE 1、MYISAM&#xff1a;全表锁&#xff0c;拥有较高的执行速度&#xff0c;不支持事务&#xff0c;不支持外键&#xff0c;并发性能差&#x…

ubuntu18.04 安装mysql(命令)

1.安装MySQL #命令1 sudo apt-get update #命令2 sudo apt-get install mysql-server 2.配置MySQL sudo mysql_secure_installation 2.2 检查mysql服务状态 systemctl status mysql.service 3.配置远程访问 在Ubuntu下MySQL缺省是只允许本地访问的 3.1 首先用根用户进入…

深度学习记录--梯度检验

数值逼近 为了对梯度进行检验&#xff0c;需要计算近似误差值来接近梯度 对于单边误差和双边误差公式&#xff0c;其中双边误差与真实梯度相差更小&#xff0c;故一般采用双边误差公式 双边误差 公式&#xff1a; 梯度检验(gradient checking) 对于成本函数&#xff0c;求出…

2018年认证杯SPSSPRO杯数学建模A题(第一阶段)海豚与沙丁鱼全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 探究海豚猎捕时沙丁鱼群的躲避运动模型 A题 海豚与沙丁鱼 原题再现&#xff1a; 沙丁鱼以聚成大群的方式来对抗海豚的捕食。由于水下光线很暗&#xff0c;所以在距离较远时&#xff0c;海豚只能使用回声定位方法来判断鱼群的整体位置&#xf…

旅游平台day02

1. 用户注册 概述&#xff1a; 常见的注册方式&#xff1a;邮箱注册、手机号注册、昵称注册、或者以上几种同时支持 本项目仅仅支持手机号注册 需求&#xff1a; 项目启动后&#xff0c;访问regist.html进入注册页面 手机号校验 前后台都需要对手机号进行校验 前端校验&am…

iOS上h5长按识别图片二维码,图片会默认放大,禁用这一默认行为

iOS上h5长按识别图片二维码&#xff0c;图片会默认放大&#xff0c;禁用这一默认行为 测试代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-widt…

ES可视化工具--ElasticHD

说明 ElasticHD 是 github 上的一个开源的项目&#xff0c;所以他没有官方网站&#xff0c;但 github 上的项目界面也可称为是它的官方界面了。 在 github 上直接搜索 ElasticHD 即可找到它&#xff0c;下面我将留下它的直接跳转链接。ElasticHD 下载 在 github 上搜索到之后…

[Error]连接iPhone调试时提示Failed to prepare the device for development.

环境&#xff1a; iPhone 7 Plus iOS 15.8 Xcode14.2 问题&#xff1a; 连接iPhone设备运行时&#xff0c;设备旁提示如下文案。 Failed to prepare the device for development. 这时强行点击运行按钮&#xff0c;会弹窗提示如下文案。 The run destination ZDMiPhone is n…

Apache Doris (六十四): Flink Doris Connector - (1)-源码编译

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. Flink与Doris版本兼容

CTF CRYPTO 密码学-3

题目名称&#xff1a;反编译 题目描述&#xff1a; 分析 题目给出一个pyc后缀的文件&#xff0c;需要使用uncompyle6模块去还原成py文件 uncompyle6简介 uncompyle6 是一个 Python 反编译器&#xff0c;它能够将 Python 字节码&#xff08;.pyc 文件&#xff09;转换回源代码&…

【征服redis7】谈谈Redis的RDB持久化方式

从现在开始&#xff0c;我们来探讨redis的一个非常重要的问题——集群&#xff0c;要讨论集群&#xff0c;我们需要先理解redis持久化数据的方法&#xff0c;因为集群本质上就是将一个集群的数据同步到其他机器上。 Redis 6的持久化机制主要有两种&#xff1a;RDB&#xff08;…

springboot开启HTTPS

目录 一、前言 HTTP和HTTPS的含义以及区别 二、域名映射 三、添加SSL证书 四、Http转Https 五、内网穿透 一、前言 我们平常写完一个接口&#xff0c;其访问一般都是使用http协议 我们最终想要的结果是使用安全的HTTPS来访问 在我们开始实现之前&#xff0c;我们要先搞明…

烟火识别视频分析系统的应用-河北消防机器人项目

在当前的智能化、信息化时代背景下&#xff0c;河北省消防部门采纳了一项革命性的技术方案 —— 烟火识别视频分析系统&#xff0c;结合消防机器人的使用&#xff0c;大幅提升了火灾预防与应急处理能力。该项目的核心在于通过高精度的视频监控&#xff0c;实时分析识别潜在的火…

【笔记】Helm-3 主题-6 Chart仓库指南

Chart仓库指南 本节介绍如何创建和使用chart仓库。在高层级中&#xff0c;chart仓库是打包的chart存储和分享的位置。 社区的Helm chart仓位于 Artifact Hub &#xff0c;欢迎加入。不过Helm也可以创建并运行您自己的chart仓库。该指南将介绍如何操作。 Artifact Hub 先决条…

Microsoft Word 设置底纹

Microsoft Word 设置底纹 References 打开文档页面&#xff0c;选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹&#xff0c;单击“确…

S32K3系列 --- 硬件I2C Mcal配置

前言 网上看到很多I2C的教程,基本都是模拟I2C,现在S32K3的芯片支持硬件I2C,我想着就配一个硬件的出来吧,这边记录一下,供大家学习。 一、I2C原理 这里主要教大家如何去配置,去使用。 原理的话可以参考这篇文章: 一文搞懂I2C通信总线_i2c通信的详细讲解-CSDN博客 I2…

Spring5深入浅出篇:Spring工厂设计模式拓展应用

Spring5深入浅出篇:Spring工厂设计模式拓展应用 简单工厂实现 这里直接上代码举例子 UserService.java public interface UserService {public void register(User user);public void login(String name, String password); }UserServiceImpl.java public class UserService…

Three.JS教程1环境搭建、场景与相机

Three.JS教程1环境搭建、场景与相机 一、Three.JS简介二、环境搭建1. 开发准备2. 安装 three.js3. 新建文件index.htmlmain.js 4. 关于附加组件5. 启动 三、创建场景1. 场景的概念2. 相机的概念3. 相机的几个相关概念&#xff08;1&#xff09;视点&#xff08;Position&#x…

Ability to Use Oracle Data Pump to Export and Import the Unified Audit Trail

Ability to Use Oracle Data Pump to Export and Import the Unified Audit Trail 从18c版本开始才能使用数据泵工具导出统一审计的记录