常见算法

Posted Jeremy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见算法相关的知识,希望对你有一定的参考价值。

常见算法

算法与数据结构是面试考察的重中之重,也是日后刷题时需要着重训练的部分。

简单的总结一下,大约有这些内容:

算法 - Algorithms

1、排序算法:快速排序、归并排序、计数排序
2、搜索算法:回溯、递归、剪枝技巧
3、图论:最短路、最小生成树、网络流建模
4、动态规划:背包问题、最长子序列、计数问题
5、基础技巧:分治、倍增、二分、贪心

数据结构 - Data Structures

1、数组与链表:单/双向链表、跳舞链
2、栈与对列
3、树与图:最近公共祖先、并查集
4、哈希表
5、堆:大/小根堆、可并堆
6、字符串:字典树、后缀树

递归与迭代的区别

递归(recursion):递归常被用来描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A)

迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)

递归是一个树结构,从字面可以其理解为重复“递推”和“回归”的过程,当“递推”到达底部时就会开始“回归”,其过程相当于树的深度优先遍历。

迭代是一个环结构,从初始状态开始,每次迭代都遍历这个环,并更新状态,多次迭代直到到达结束状态。

# 理论上递归和迭代时间复杂度方面是一样的,但实际应用中(函数调用和函数调用堆栈的开销)递归比迭代效率要低。

链接:https://www.jianshu.com/p/32bcc45efd32
来源:简书

 

 

算法的时间复杂度和空间复杂度

  • 时间复杂度和空间复杂度是用来评价算法效率高低的2个标准。

  • 时间复杂度:就是说执行算法需要消耗的时间长短,越快越好。比如你在电脑上打开计算器,如果一个普通的运算要消耗1分钟时间,那谁还会用它呢,还不如自己口算呢。

  • 空间复杂度:就是说执行当前算法需要消耗的存储空间大小,也是越少越好。本来计算机的存储资源就是有限的,如果你的算法总是需要耗费很大的存储空间,这样也会给机器带来很大的负担。

时间复杂度的计算

表示方法

我们一般用“大O符号表示法”来表示时间复杂度:T(n) = O(f(n)) n是影响复杂度变化的因子,f(n)是复杂度具体的算法。

常见的时间复杂度量级

  • 常数阶O(1)

  • 线性阶O(n)

  • 对数阶O(logN)

  • 线性对数阶O(nlogN)

  • 平方阶O(n²)

  • 立方阶O(n³)

  • K次方阶O(n^k)

  • 指数阶(2^n)

常见的时间复杂度(按效率排序) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)

接下来再看一下不同的复杂度所对应的算法类型。

img

常数阶O(1)

int a = 1;
int b = 2;
int c = 3;

线性阶O(n)

for(i = 1; i <= n; i++) {
  j = i;
  j++;
}

对数阶O(logN)

int i = 1;
while(i < n) {
   i = i * 2;
}

线性对数阶O(nlogN)

for(m = 1; m < n; m++) {
   i = 1;
   while(i < n) {
       i = i * 2;
  }
}

平方阶O(n²)

for(x = 1; i <= n; x++){
  for(i = 1; i <= n; i++) {
      j = i;
      j++;
  }
}

空间复杂度计算

空间复杂度 O(1)

如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)。

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)。

空间复杂度 O(n)

int[] m = new int[n]
for(i = 1; i <= n; ++i) {
  j = i;
  j++;
}

这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,后面虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)。

十大经典排序算法

# 冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
import sys
sys.setrecursionlimit(1000000)
## 冒泡排序 (******)
### 时间复杂度:O(n^2)
def Bubble_sort(li):
   for i in range(len(li)-1):
       for j in range(len(li)-1-i):
           if li[j] > li[j+1]:
               li[j], li[j+1] = li[j+1], li[j
    return li
               
               
## 选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
#### 时间复杂度:O(n^2)
def select_sort(li):
   for i in range(len(li)):
       minLoc = i ###i = 0
       for j in range(i+1, len(li)):
           if li[j] < li[minLoc]:
               li[j], li[minLoc] = li[minLoc], li[j]
    return li
               

##### 插入排序(打扑克牌)
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
#### 时间复杂度: O(n^2)
def insert_sort(li):
   for i in range(1, len(li)):
       tmp = li[i]
       j = i - 1
       while j >=0 and li[j] > tmp:
           li[j+1] = li[j]
           j = j - 1
       li[j+1] = tmp


## 快速排序
1、从数列中挑出一个元素,称为 "基准"(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
                                           
def partition(li, left, right):
   tmp = li[left]
   while left < right:
       while left < right and li[right] >= tmp:
           right = right - 1
       li[left] = li[right]
       while left < right and li[left] <= tmp:
           left = left + 1
       li[right] = li[left]
   li[left] = tmp

   return left                                            
                                                                               
## 时间复杂度:O(nlogn)
def quick_sort(li, left, right):
   if left < right:
       mid = partition(li, left, right)
       quick_sort(li, left, mid-1)
       quick_sort(li, mid+1, right)
    return li
             
                                           
# 计算时间复杂度
import time,random
li = [random.randint(1,100) for _ in range(100000)]
start = time.time()
quick_sort(li, 0, len(li)-1)
cost = time.time() - start
print(\'quick_sort:%s\' % (cost))

import time,random
li = [random.randint(1,100) for _ in range(100000)]
start = time.time()
Bubble_sort(li)
cost = time.time() - start
print(\'bubble_sort:%s\' % (cost))

import time,random
li = [random.randint(1,100) for _ in range(100000)]
start = time.time()
insert_sort(li)
cost = time.time() - start
print(\'insert_sort:%s\' % (cost))

算法-力扣(LeetCode)

链表反转

反转一个单链表:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL  
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
# 双向指针迭代
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 申请两个节点,pre和 cur,pre指向None
pre = None
cur = head
# 遍历链表,while循环里面的内容其实可以写成一行
# 这里只做演示,就不搞那么骚气的写法了
while cur:
# 记录当前节点的下一个节点
tmp = cur.next
# 然后将当前节点指向pre
cur.next = pre
# pre和cur节点都前进一位
pre = cur
cur = tmp
return pre

   # 2、递归解法
   class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 递归终止条件是当前为空,或者下一个节点为空
if(head==None or head.next==None):
return head
# 这里的cur就是最后一个节点
cur = self.reverseList(head.next)
# 这里请配合动画演示理解
# 如果链表是 1->2->3->4->5,那么此时的cur就是5
# 而head是4,head的下一个是5,下下一个是空
# 所以head.next.next 就是5->4
head.next.next = head
# 防止链表循环,需要将head.next设置为空
head.next = None
# 每层递归函数都返回cur,也就是最后一个节点
return cur
   
来源:力扣(LeetCode)

反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

示例
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

简单实现
class Solution:
   def reverseString(self, s):
       s.reverse()
       
# 1、递归实现
class Solution:
   def reverseString(self, s):
       def helper(left, right):
           if left < right:
               s[left], s[right] = s[right], s[left]
               helper(left + 1, right - 1)

       helper(0, len(s) - 1)
       
# 2、一步实现
class Solution:
   def reverseString(self, s: List[str]) -> None:
       """
      Do not return anything, modify s in-place instead.
      """
       # one step by python
       s[:] = s[::-1]
       

来源:力扣(LeetCode)

二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例: 给定二叉树 [3,9,20,null,null,15,7]

   3
  / \\
 9  20
   /  \\
  15   7

方法:递归

直观的方法是通过递归来解决问题。在这里,我们演示了 DFS(深度优先搜索)策略的示例。

class Solution:
   def maxDepth(self,root): # root子节点
       if root is None:
           return 0
       else:
           left_height = self.maxDepth(root.left)
           right_height = self.maxDepth(root.right)
           return max(left_height,right_height)+1  #左右对比 基于子节点+1

整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例:

输入: 123
输出: 321
方法:字符串的反转,记录符号位。
class Solution:
   def reverse(self, x: int) -> int:
       flag = -1 if x < 0  else 1
       res = flag * int(str(abs(x))[::-1])
       return res if (-2**31)<=res<=(2**31-1) else 0

作者:powcai

删除排序中数组中的重复项

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4
你不需要考虑数组中超出新长度后面的元素。

思路:

用两个指针,指向第一个和第二个元素,如果他们相等,删除第二个元素。指针还指向原来的位置,继续比较。不等的话,两个指针位置都加一。遍历结束即可。

解题思路 题意:删除有序数组重复项,把去重后的数字放在输入数组的前面 n 个位置,返回 n.

看到题目标题的第一反应,当然是用 set !set 就是为了实现去重的。但是题目要求我们进行原地操作,并且时间复杂度是 O(1)O(1),因此就不能开辟另外的空间。

双指针 题目需要我们把去重后的结果保存到原本的数组中,所以想到必须有一个指针指向当前需要把结果放在哪个位置。还要一个指针指向当前应该放到哪个元素。

慢指针作为基准,快指针用于寻找与慢指针不同的元素。 如果快指针和慢指针指向的元素不等,则把快指针指向的元素放到慢指针的下一个位置。 慢指针右移,把新的元素作为基准。

作者:fuxuemingzhu 链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/solution/fu-xue-ming-zhu-shuang-zhi-zhen-jie-fa-s-hp71/

class Solution:
   def removeDuplicates(self, nums: List[int]) -> int:
       pre,cur=0,1 # 指针初始指向数
       while cur<len(nums):      
           if nums[pre]==nums[cur]:
               nums.pop(cur) # 取出重复数
           else:
               pre,cur=pre+1,cur+1  # 交换并加一往后
       return len(nums)# 数组的长度

# 第二种解法
class Solution(object):
   def removeDuplicates(self, nums):
       N = len(nums)
       left = 0
       for right in range(1, N):
           if nums[right] != nums[left]:
               left += 1
               nums[left] = nums[right]
       return left + 1

存在重复元素

给定一个整数数组,判断是否存在重复元素。

如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false

示例:

输入: [1,2,3,1]
输出: true
   
输入: [1,2,3,4]
输出: false

集合判断法:利用Python独有的数据类集合特性。 用nums = list(set(nums))就可以轻松排除了

class Solution(object):
   def containsDuplicat(self,nums:list[int]) ->bool:
       if len(self(nums) == len(nums))
      return Flase
       else:
           return True
       

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

示例:

输入: [4,1,2,1,2]
输出: 4

思路:通过数学的解法

先通过set把数据去重,然后把所有的值相加*2去减之前的值,剩下的值就是答案

1、数学
class Solution(object):
   def singleNumber(self, nums):
       return 2 * sum(set(nums)) - sum(nums)
   
2、counter函数
from collections import Counter
class Solution:
   def singleNumber(self, nums: List[int]) -> int:
       datas = Counter(nums)
       for each in datas:
           if datas[each] == 1: return each
以上是关于常见算法的主要内容,如果未能解决你的问题,请参考以下文章

hadoop的mapreduce常见算法案例有几种

以下代码片段的算法复杂度

有人可以解释啥是 SVN 平分算法吗?理论上和通过代码片段[重复]

片段(Java) | 机试题+算法思路+考点+代码解析 2023

Android 实用代码片段

Android 实用代码片段