面试常考算法

背景

一般一些大厂面试都会以一个算法题结尾 那么笔者总结了下哪些类型的算法会比较常考

排序算法

排序算法是最常考的了    简单描述下八大排序算法的规则

希尔排序 

希尔排序就是为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序。
采⽤插入排序的⽅法,先让数组中任意间隔为 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
}

还会考一些智力题

笔者就不多描述了

面试智力题精选-CSDN博客

对于有一个生成1-5随机数的接口 要你实现1-7随机数的接口的问题

就是说每个值出现的概率要一样

那么就是 5 * (rand5 - 1) + rand5 这样1-25每个值出现的概率都是25分之一 

然后value % 7 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值