背景
一般一些大厂面试都会以一个算法题结尾 那么笔者总结了下哪些类型的算法会比较常考
排序算法
排序算法是最常考的了 简单描述下八大排序算法的规则
希尔排序
希尔排序就是为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序。
采⽤插入排序的⽅法,先让数组中任意间隔为 h 的元素有序,刚开始 h 的⼤大⼩小可以是 h = n / 2,接着让 h = n / 4,让 h ⼀一直缩⼩小,当 h = 1 时,也就是此时数组中任意间隔为1的元素有序, 此时数组就是有序的了
不稳定排序 时间复杂度n的1.3次方
func shellSort(nums []int){
n := len(nums)
//做出间隔
for gap := n / 2; gap > 0; gap /= 2 {
for i := 0; i < gap; i++{
//插入算法
for k := gap; k < n; k += gap{
for j := k - gap; j >= 0; j -= gap{
if(nums[j + 1] < nums[j]){
nums[j], nums[j + 1] = nums[j + 1], nums[j]
}else{
break
}
}
}
}
}
}
插入排序
具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置
稳定排序 时间复杂度n的平方
func insertSort(nums []int){
n := len(nums)
for i := 1; i < n; i++{
for k := i - 1; k >= 0; k--{
if(nums[k + 1] < nums[k]){
nums[k], nums[k + 1] = nums[k + 1], nums[k]
}else{
break
}
}
}
}
冒泡排序
每次把最大的数移到最后
稳定排序 时间复杂度n的平方
func bubbleSort(nums []int) {
n := len(nums)
for i := 1; i < n; i++{
for k := 0; k < n - i; k++{
if(nums[k] > nums[k + 1]){
nums[k], nums[k + 1] = nums[k + 1], nums[k]
}
}
}
}
选择排序
每次选择最小的数放在前面
不稳定排序 时间复杂度n的平方
func selectSort(nums []int){
n := len(nums)
for i := 0; i < n - 1; i++{
minIndex := i
for k := i + 1; k < n; k++{
if(nums[k] < nums[minIndex]){
minIndex = k
}
}
nums[minIndex], nums[i] = nums[i], nums[minIndex]
}
}
堆排序
堆排序的思想就是先将待排序的序列建成大根堆,使得每个父节点的元素大于等于它的子节点。此时整个序列最大值即为堆顶元素,我们将其与末尾元素交换,使末尾元素为最大值,然后再调整堆顶元素使得剩下的 n−1 个元素仍为大根堆,再重复执行以上操作我们即能得到一个有序的序列
不稳定排序 时间复杂度nlogn
func bigHeapSort(nums []int){
n := len(nums) - 1
//建立大顶堆
for i := n / 2; i >= 0; i--{
bigHeapSortDown(nums, i, n)
}
for i := n; i > 0; i--{
nums[i], nums[0] = nums[0], nums[i]
bigHeapSortDown(nums, 0, i - 1)
}
}
func bigHeapSortDown(nums []int, num int, n int){
if(2 * num + 1 > n){
return
}
if nums[2 * num + 1] > nums[num] {
if (2 * num + 2 <= n) && nums[2 * num + 2] > nums[2 * num + 1] {
nums[num], nums[2 * num + 2] = nums[2 * num + 2], nums[num]
bigHeapSortDown(nums, 2 * num + 2, n)
}else{
nums[num], nums[2 * num + 1] = nums[2 * num + 1], nums[num]
bigHeapSortDown(nums, 2 * num + 1, n)
}
}else if (2 * num + 2 <= n) && nums[2 * num + 2] > nums[num] {
nums[num], nums[2 * num + 2] = nums[2 * num + 2], nums[num]
bigHeapSortDown(nums, 2 * num + 2, n)
}
}
归并排序
归并排序利用了分治的思想来对序列进行排序。对一个长为 n 的待排序的序列,我们将其分解成两个长度为 n/2的子序列。每次先递归调用函数使两个子序列有序,然后我们再线性合并两个有序的子序列使整个序列有序。有点后序排序的思路
求mid,递归排左边、右边,合并
稳定排序 时间复杂度nlogn
func mergeSort(nums []int, left, mid, right int){
if(left >= right){
return
}
if(mid > left){
mergeSort(nums, left, (left + mid) / 2, mid)
}
if(right > mid + 1){
mergeSort(nums, mid + 1, (right + mid + 1) / 2 ,right)
}
temp := make([]int, right - left + 1)
i, j, k := left, mid + 1, 0
for i <= mid&& j <= right{
if(nums[i] <= nums[j]){
temp[k] = nums[i]
i++
}else{
temp[k] = nums[j]
j++
}
k++
}
for i <= mid{
temp[k] = nums[i]
i++
k++
}
for j <= right{
temp[k] = nums[j]
j++
k++
}
copy(nums[left:], temp)
}
快速排序
快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧
不稳定排序 时间复杂度nlogn
func quickSort(nums []int, left, right int) {
if left >= right {
return
}
num := nums[left]
i, k := left, right
for i < k {
for i < k && nums[k] >= num {
k--
}
nums[i] = nums[k]
for i < k && nums[i] <= num {
i++
}
nums[k] = nums[i]
}
nums[i] = num
quickSort(nums, left, i-1)
quickSort(nums, i+1, right)
}
计数排序
通过统计元素数量来实现排序,通常应用于整数数组
稳定算法 时间复杂度n
func countSort(nums []int){
maxNum := 0
for _, value := range nums{
if(value > maxNum){
maxNum = value
}
}
countList := make([]int, maxNum + 1)
for _, value := range nums{
countList[value]++
}
first := 0
for index, value := range countList{
for i := 0; i < value; i++{
nums[first] = index
first++
}
}
}
桶排序
将数据分到有限数量的桶里,每个桶再分别排序
稳定排序 时间复杂度n+k
func bucketSort(nums []int){
//得到最大最小值
minNum := nums[0]
maxNum := nums[0]
for _, value := range nums{
if(value < minNum){
minNum = value
}
if(value > maxNum){
maxNum = value
}
}
//分为5个桶
bucketNum := 5
bucketLen := (maxNum - minNum) / bucketNum
bucket := make([][]int, bucketNum)
//插入到桶中
for _, value := range nums{
index := (value - minNum) / bucketLen
if(value == maxNum){
index--
}
bucket[index] = append(bucket[index], value)
}
//桶内排序
for _, bucketNums := range bucket{
bubbleSort(bucketNums)
}
i := 0
for _, bucketNums := range bucket{
for _, num := range bucketNums{
nums[i] = num
i++
}
}
}
基数排序
与计数排序一致,也通过统计个数来实现排序。在此基础上,基数排序利用数字各位之间的递进关系,依次对每一位进行排序,从而得到最终的排序结果
稳定排序 时间复杂度n+k
字符串处理
链表
反转链表
type ListNode struct {
Val int
Next *ListNode
}
func reverseList(head *ListNode) *ListNode {
if(head == nil || head.Next == nil){
return head
}
newHead := doReverse(head, head.Next)
head.Next = nil
return newHead
}
func doReverse(lastNode, node *ListNode) *ListNode{
if(node.Next == nil){
node.Next = lastNode
return node
}
nextNode := node.Next
node.Next = lastNode
return doReverse(node, nextNode)
}
数据结构
前中后序遍历
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func preorderTraversal(root *TreeNode) []int {
var back []int
doTraversal(root, &back)
return back
}
//前序
func doTraversal(node *TreeNode, back *[]int){
if(node == nil){
return
}
*back = append(*back, node.Val)
if(node.Left != nil){
doTraversal(node.Left, back)
}
if(node.Right != nil){
doTraversal(node.Right, back)
}
}
//中序
func doTraversal(node *TreeNode, back *[]int){
if(node == nil){
return
}
if(node.Left != nil){
doTraversal(node.Left, back)
}
*back = append(*back, node.Val)
if(node.Right != nil){
doTraversal(node.Right, back)
}
}
//后序
func doTraversal(node *TreeNode, back *[]int){
if(node == nil){
return
}
if(node.Left != nil){
doTraversal(node.Left, back)
}
if(node.Right != nil){
doTraversal(node.Right, back)
}
*back = append(*back, node.Val)
}
一般都是考树结构 比如红黑树
贪心算法
回溯算法
动态规划
深度优先搜索
分治算法
LRU
topN的排序
func topN(nums []int, n int) {
for i := 0; i < len(nums); i++ {
if i < n {
for k := i; k > 0; k-- {
if nums[k] <= nums[k-1] {
break
}
nums[k-1] = nums[k]
}
} else if nums[i] > nums[n-1] {
nums[n-1] = nums[i]
for k := n - 1; k > 0; k-- {
if nums[k-1] >= nums[k] {
break
}
nums[k], nums[k-1] = nums[k-1], nums[k]
}
}
}
}
双指针
接雨水
func trap(height []int) (ans int) {
n := len(height)
if n == 0 {
return
}
leftMax := make([]int, n)
leftMax[0] = height[0]
for i := 1; i < n; i++ {
leftMax[i] = max(leftMax[i-1], height[i])
}
rightMax := make([]int, n)
rightMax[n-1] = height[n-1]
for i := n - 2; i >= 0; i-- {
rightMax[i] = max(rightMax[i+1], height[i])
}
for i, h := range height {
ans += min(leftMax[i], rightMax[i]) - h
}
return
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
还会考一些智力题
笔者就不多描述了
对于有一个生成1-5随机数的接口 要你实现1-7随机数的接口的问题
就是说每个值出现的概率要一样
那么就是 5 * (rand5 - 1) + rand5 这样1-25每个值出现的概率都是25分之一
然后value % 7