算法(笔记十四,后续会继续更新)

时间复杂度和空间复杂度

时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。****

推导大O阶有一下三种规则:

  1. 用常数1取代运行时间中的所有加法常数

  2. 只保留最高阶项

  3. 去除最高阶的常数

    • 常数阶
    let sum = 0, n = 10; // 语句执行一次 
    let sum = (1+n)*n/2; // 语句执行一次 
    console.log(`The sum is : ${sum}`) //语句执行一次 
    

    这样的一段代码它的执行次数为 3 ,然后我们套用规则1,则这个算法的时间复杂度为O(1),也就是常数阶。

  • 线性阶
let i =0; // 语句执行一次 
while (i < n) { // 语句执行n次 
  console.log(`Current i is ${i}`); //语句执行n次
  i++; // 语句执行n次
}

这个算法中代码总共执行了 3n + 1次,根据规则 2->3,因此该算法的时间复杂度是O(n)。

  • 对数阶
let number = 1; // 语句执行一次 
while (number < n) { // 语句执行logn次
  number *= 2; // 语句执行logn次
}

上面的算法中,number每次都放大两倍,我们假设这个循环体执行了m次,那么2^m = nm = logn,所以整段代码执行次数为1 + 2*logn,则f(n) = logn,时间复杂度为O(logn)。

  • 平方阶
for (let i = 0; i < n; i++) { // 语句执行n次 
  for (let j = 0; j < n; j++) { // 语句执行n^2次 
     console.log('I am here!'); // 语句执行n^2
  }
}

上面的嵌套循环中,代码共执行 2*n^2 + n,则f(n) = n^2。所以该算法的时间复杂度为O(n^2 )

img

img

算法1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。


示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

1.暴力枚举(最蠢得办法) O(n*n)

class solution(){

public int []twoSum(int []nums,int target){

for(int i=0;i< nums.lenth-1){

//因为j遍历到最后一个时,i是在j前一个

for(int j=i+1;j<len;j++){

if(nums[i]+nums[j]==target){

return new int[]{i,j}

}

}

}

//没答案,这里抛出异常

}

}

2.哈希表 O(n)

class solution(){

public int []twoSum(int []nums,int target){

int len =nums.length;

Map<Integer,Integer> hashMap = new HashMap<>(len-1);

//这里长度为len-1是因为通过target查找 长度为最后一个(len-1)时为特殊情况,即返回得两个数字/应该是最后两位

hashMap.put(nums[0],0);

for( int i=1;i<len;i++){

int another= target-nums[i]

// {1,2,3,4,5} target =5 i=1第一个元素为(2,0) 则查找哈希表中是否存在 5-2->3的键

if(hashMap.containsKey(another)){

//循环到了 i= 2 则查找哈希表中是否存在 5-3->2的键 只要通过target-nums[i]这个值然后去对比hash表中是否有这个之前存入的这个key,如果有则返回现在正在循环的i的值和hash表之前存入的这个key值,如果没有则将i+1 继续循环推进数组中的下一个元素并保存到hash表中,然后去查看之前的key值是否= target-nums[i]

return new int[]{i,hashMap.get(another)};

}

//下一轮继续添加num数组中的值到hash表中 添加(2,1)

hashMap.put(num[i],i)

}

//没答案,这里抛出异常

}

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-waoW9mO7-1632392819416)(C:\Users\涛大爷的笔记本\AppData\Roaming\Typora\typora-user-images\image-20210820220852444.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u7DSIzrv-1632392819419)(C:\Users\涛大爷的笔记本\AppData\Roaming\Typora\typora-user-images\image-20210820220902985.png)]

算法2:

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTGHZ839-1632392819421)(C:\Users\涛大爷的笔记本\AppData\Roaming\Typora\typora-user-images\image-20210821191604807.png)]

/**

\* Definition for singly-linked list.

\* public class ListNode {

\* int val;

\* ListNode next;

\* ListNode() {}

\* ListNode(int val) { this.val = val; }

\* ListNode(int val, ListNode next) { this.val = val; this.next = next; }

\* }

*/

public class Solution {

//1.L1的长度>=L2的长度

//2.L2的长度>L1的长度

//末位需要进位/末位不需要进位

//2 4 6

//3 5 6 -> 5 9 1 2

public ListNode addTwoNumbers(ListNode l1,ListNode l2){

ListNode head1 = l1;

ListNode head2 = l2;

``

while(head1!=null){

if(head2!=null){

//这里对两个链表进行对应相加,指针推进

head1.val+=head2.val;

head2=head2.next;

}

// L1遍历完了,而L2没有遍历完 这是的情况是L2的长度>L1的长度,所以这里吧L2的值赋给L1

跳出循环进入merge函数

if(head1.next==null && head2!=null){

head1.next=head2;

break;

}

head1=head1.next;

}

merge(l1);

return l1;

}

``

//进位

public void merge(ListNode head){

while(head!=null){

if(head.val >=10){

head.val=head.val%10;

//这里是第三种情况,需要进位,因为传过来的L1的下一位没有了,所以必须空指针异常,这里新增加一个节点

if(head.next==null){

head.next = new ListNode(0);

}

head.next.val+=1;

}

head=head.next;

}

}

}

//"123abcd"  ->dcb123a
public class test1 {
    public static void main(String[] args) {

        System.out.println(reverse2("123abcd",3));
    }

    public static String reverse2(String s,int n)
    {

        int length = 3;
        String ss = s.substring(s.length()-3);
        String sss = s.substring(0,s.length()-3);

        String reverse = "";  //新建空字符串

        for (int i = ss.length()-1; i >= 0; i--)  {

            reverse = reverse+ss.charAt(i) ;//在新字符串前面添加读取字符,实现翻转
        }
       
        return reverse;
    }
}
//字符串中的一些常用函数:连接concat()、提取substring()、charAt()、length()、equals()、equalsIgnoreCase()等等

4.冒泡排序

时间复杂度: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0KSJNwf-1632392819424)(https://math.jianshu.com/math?formula=O(N%5E2)])
空间复杂度: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kIi91Vck-1632392819426)(https://math.jianshu.com/math?formula=O(1)])
稳定性:稳定

每一趟遍历,将一个最大的数移到序列末尾。

public void sort(){

	 for(int i=0;i<arr.length;i++){

		for(int j=0;j<arr.length-i-1;j++){

			  if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }

}

}

}

img

img

5.插入排序

所以如果数组本来就是有序的最好情况下时间复杂度为O(n)

如果数组恰好是倒序最坏情况下时间复杂度为O(n2)

public void sort() {
	// 假定第一个元素是已经排好序的, 从第二个元素循环整个数组,从第二位开始遍历,所以i=1
	for (int i = 1; i < a.length; i++) {
		// 取出当前的值
		int next = a[i];
		// 记录当前的元素的索引
		int j = i;
		// 循环将当前的值与前面的值进行比较,如果当前的值比前面元素的值小,则将前面的值向后移,再将索引向前移动,直到移到数组的开头索引0位置  若是当前值比前面元素大,则直接插入,此时前面的已经排好序,之前比当前值大的元素的数组下标索引也已经往后移了一位
		while (j > 0 && a[j - 1] > next) {
			a[j] = a[j - 1];
			j--;
		}
		// 将当前的值放到合适的位置.
		a[j] = next;
	}
}

img

1. 从第二位开始遍历,

2. 当前数(第一趟是第二位数) 与前面的数依次比较,如果前面的数大于当前数,则将这个数放在当前数的位置上,当前数的下标-1

3. 重复以上步骤,直到当前数不大于前面的某一个数为止,这时,将当前数,放到这个位置

1-3步就是保证当前数的前面的数都是有序的,内层循环的目的就是将当前数插入到前面的有序序列里

4. 重复以上3步,直到遍历到最后一位数,并将最后一位数插入到合适的位置,插入排序结束。

5.链表反转

题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

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

public int getValue() {
return value;
}

public Node getNode() {
return node;
}

public void setNode(Node node) {
this.node = node;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRw0eBt6-1632392819431)(C:\Users\涛大爷的笔记本\AppData\Roaming\Typora\typora-user-images\image-20210829220936160.png)]

public ListNode ReverseList(ListNode head){

if(head==null){

return null;

}

while**(nowNode !=** null**){**

ListNode nowNode =head;

ListNode preNode =null;

while(nowNode!=null){

ListNode temNode=nowNode.next;*//保存下一个结点*

nowNode =preNode; *//当前结点指向前一个结点*

//为下一次循环做准备

preNode = nowNode;// 往后移一位

nowNode =temNode;//往后移一位

}

return preNode; //因为preNode.next==nowNode 如果是最后一个节点了,nowNode就会指向null

}

6.快速排序(核心思想:找到一个中心轴,左边的部分数组小于这个中心轴,右边的部分数组大于这个中心轴)

第一步:对传进来的字符串数据判断是否为空,或者长度是否为0

第二步**:查看左指针是否比右指针大**(这样子明显是对不合理指针移动进行判断)

第三步:因为最终判断要和key交换位置的那个元素是根据左指针右移,右指针左移,直到两个指针重合(这是大条件)

第四步:必须先判断右指针,如果在循环中(这里要注意arr[r]有等于key的情况)右指针对应的元素大于刚刚传进来的key(arr[left]==arr[0]),则右指针左移一位,这表示这个元素大于key不需要动它的位置(直到右指针指向的这个元素小于key,则不满足核心思想,需要换位置)

第五步:判断左指针,如果在循环中(这里要注意arr[l]有等于key的情况)左指针对应的元素是小于刚刚传进来的key(arr[left]==arr[0]),则左指针右移一位,这表示这个元素小于key不需要动它的位置(直到左指针指向的这个元素大于于key,则不满足核心思想,需要换位置)

第六步:此时左指针=右指针不满足上面的循环并跳出循环,这个左右指针所在的位置就是中心轴位置,这里就需要交换位置 (key=2 {3,1} --> {1,3})

第七步:因为最开始是默认arr[0]为中心轴,所以需要交换位置,arr[left]=arr[r]//你不是中心轴你要和到第一个去,arr[l]=key//再把key的值赋给arr[l]这里就是把中心轴位置的元素赋值给arr[l],让arr[l]变成中心轴

第八步:根据核心思想,递归左数组和右数组重复操作(这里以中心轴为界限),左边排序完了,排序右边

public static void quick(int[]arr,int left,int right){
		if(arr ==null || arr.length==0 ){
			return;
		}
		if(left>right){
			return;
		}
		int key =arr[left];
		int l = left;
		int r = right;
		
while(l!=r){
		while(arr[r]>=key && r>l){
			r--;
		}
		while(arr[l]<=key &&l<r){
			l++;
		}
		if(l<r){
			int temp = arr[l];
			arr[l]=arr[r];
			arr[r]=temp;
		}
	}
	arr[left]=arr[l];
	arr[l]=key;
	quick(arr,left,l-1);//左数组递归 
	quick(arr,l+1,right);//右数组递归
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMWyhBW8-1632392819433)(C:\Users\涛大爷的笔记本\AppData\Roaming\Typora\typora-user-images\image-20210921225727684.png)]

第一步:默认最左边这个元素为中心轴,创建一个左指针,一个右指针,首先从右指针开始,去从右往左移,如果指针指向的这个元素大于或等于中心轴元素,right–,继续从右往左移动,如果指针指向的这个元素小于中心轴元素,则放到中心轴的左边,

第二步:右指针指向完了,左指针开始往右移动,最开始左指针指向的元素等于和中心轴,左指针指向的这个元素小于或等于中心轴,则left++,继续从左往右移动,如果指针指向的这个元素大于中心轴,则放到中心轴的右边,

第三步:直到左指针与右指针重合相等,就把中心轴放到指针指向重合的那个位置上,这样子就得到了左子序列和右子序列,左边的比中心轴要小,右边的比中心轴大,就完成了第一次排序

第四步:但是左右子序列还没有完全排好序,所以递归去调用快排方法,左子序列:从传进来的L到中心轴-1开始排序,右子序列:从中心轴+1到传进来的R

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值