时间复杂度和空间复杂度
时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。****
推导大O阶有一下三种规则:
-
用常数1取代运行时间中的所有加法常数
-
只保留最高阶项
-
去除最高阶的常数
- 常数阶
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 = n
即m = 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 )
算法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;
}
}
}
}
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;
}
}
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