主要是圍繞高質量程式碼,完成性、規範性和魯棒性。
測試用例的編寫,通常情況,我們為了完成單元測試覆蓋率都是草草了事,學習這一章我們可以發現,我們之前的測試用例實用性很差,只有正常情況的用例,並且很多時候只有一個。之後我也跟著用例從三方面來執行:1、功能測試,2、負面測試,3、邊界測試。負面測試可以理解為錯誤測試,異常情況的測試。
面試題11:數值的整數次方
leetcode-cn.com/problems/shu-zhi-d...
按照題意,很容易寫出來基本的邏輯程式碼。
func myPow(x float64, n int) float64 {
var result = 1.0
if n < 0 {
x = 1 / x
n = -n
}
for i := 0; i < n; i++ {
result *= x
}
return result
}
可惜,會提示超時,尷尬的很,於是乎看了一遍原文的邏輯,重新實現一遍,邏輯如下:
快速冪 演算法
演算法流程:
當 x = 0 時:直接返回 0 (避免後續 x = 1 / x操作報錯)。
初始化 res = 1
當 n < 0 時:把問題轉化至 n≥0 的範圍內,即執行 x = 1/x ,n = - n ;
迴圈計算:當 n = 0時跳出;
當 n & 1 == 1 時:將當前 x乘入 res (即 res *= x);
執行 x = x^2 (即 x *= x)
執行 n 右移一位(即 n >>= 1)。
返回 res
func myPow(x float64, n int) float64 {
var result = 1.0
if n < 0 {
x = 1 / x
n = -n
}
for ; n > 0; {
if n&1 == 1 { //又學了一招位運算,等價於 n % 2 == 1
result *= x
}
x *= x
n >>= 1 // 指數減半
}
return result
}
面試題12: 列印1到最大的n位數
leetcode-cn.com/problems/da-yin-co...
雖然能透過leetcode,但是不符合書本上出題的目的,因為要考慮大數的情況下才行,書上給出的思路是轉換成字串來進行輸出
func printNumbers(n int) []int {
res := make([]int,0)
var count = 1
for i := 1; i < n; i++ {
count *= 10
}
for i := 0; i < count; i++ {
res = append(res,i)
}
return res
}
這不包括大數,不是書本出題的本意,修改如下:
考察的是程式碼的完整性 返回就不能是[]int 而應該是[]string,看具體程式碼,沒辦法在leetcode驗證,只能自己列印日誌看了。
func printNumbers(n int) []string {
if n <= 0 {
return nil
}
res := make([]int, 0)
result := make([]string, 0)
number := make([]int, n)
for !increment(number) {
for i := 0; i < len(number); i++ {
res = append(res, number[i])
}
}
//陣列按n位切分成而為陣列
temp := make([]int, 0)
for i := 0; i < len(res); i++ {
temp = append(temp, res[i])
if (i+1)%n == 0 { // 3餘數是0 。說明是n的整數倍
result = append(result, getString(temp))
temp = make([]int, 0)
}
}
return result
}
func getString(number []int) string {
var res string
isBegin := false
for i := 0; i < len(number); i++ {
if number[i] != 0 {
isBegin = true
}
if isBegin {
res += fmt.Sprintf("%d", number[i])
}
}
return res
}
func increment(number []int) bool {
var isOverFlow = false //是否溢位結束
var nTakeOver = 0 // 進位
var nLength = len(number)
for i := nLength - 1; i >= 0; i-- {
nSum := number[i] + nTakeOver
if nLength-1 == i {
nSum++
}
if nSum >= 10 {
if i == 0 {
isOverFlow = true
} else {
nSum -= 10
nTakeOver = 1
number[i] = nSum
}
} else {
number[i] = nSum
break
}
}
return isOverFlow
}
面試題13: 在O(1)時間刪除連結串列節點
leetcode-cn.com/problems/shan-chu-...
leetcode要求比書本上的低,沒有時間複雜度的要求
// 先不考慮O(1),遍歷是最直接的
func deleteNode(head *ListNode, val int){
if head == nil {
return
}
for head.Next != nil {
if head.Next.Val == val {
head.Next = head.Next.Next
}
head = head.Next
}
}
連結串列少不了假頭、新連結串列、雙指標等輔助,目前就是使用假頭的最佳例項,和書上的不一樣哈,課本上的刪除就完了,不用返回,力扣是要刪除後返回頭節點的
func deleteNode(head *ListNode, val int) *ListNode {
//for head.Next != nil {
// if val == head.Next.Val {
// head = head.Next.Next//刪除節點 課本上這就結束了。
// }
//}
dummy := &ListNode{}// 生成一個新連結串列
tail := dummy
for head != nil {
if head.Val != val {
//新增到新的連結串列中
tail.Next = head
tail = head
}
head = head.Next
}
tail.Next = nil //設定尾部為空
return dummy.Next
}
上面的例子中,空間複雜度是O(n),想要是1的話,只能按照課本上的引數傳遞才行,否者不可實現
func deleteNode(head *ListNode, deleteNode *ListNode) {
if head == nil || deleteNode == nil {
return
}
if deleteNode.Next != nil {//不是尾節點
//刪除的節點下一個值,替換需要刪除的節點,然後刪除下一個節點,等同於刪除當前節點
deleteNode.Val = deleteNode.Next.Val
deleteNode.Next = deleteNode.Next.Next
}else if head == deleteNode {//刪除的是頭節點,只有一個節點
head = nil
}else{ // 多個節點,刪除的是尾節點 第一個if條件難道不包括嗎?
for head.Next != deleteNode {
head = head.Next
}
head.Next = nil
}
}
面試題14: 調整陣列順序使奇數位於偶數前面
leetcode-cn.com/problems/diao-zhen...
思路:兩個指標,一個指向頭,一個指向尾部,兩邊同時向中間移動,如果頭指標的值偶數,尾指標是奇數就交換
通用性:判斷條件改為函式來判斷,分離出來nums[left]&1 == 1和nums[right]&1 == 0換成函式就可以了,很簡單,自行新增一下。
func exchange(nums []int) []int {
if len(nums) == 0 {
return nil
}
var left = 0
var right = len(nums) - 1
for left < right {
for left < right && nums[left]&1 == 1 { //奇數,右移left
left++
}
for left < right && nums[right]&1 == 0 { //偶數,左移right
right--
}
if left < right { //交換
nums[left], nums[right] = nums[right], nums[left]
}
}
return nums
}
面試題15: 連結串列中倒數第k個節點
leetcode-cn.com/problems/lian-biao...
思路:兩個指標,一個指向頭,一個先走k步,簡稱:快慢指標
func getKthFromEnd(head *ListNode, k int) *ListNode {
if head != nil {
return nil
}
var first = head
var second = head
for i := 0; i < k; i++ {
second = second.Next
}
for second != nil {
first = first.Next
second = second.Next
}
return first
}
面試題16: 反轉連結串列
leetcode-cn.com/problems/UHnkqh/
這題,如果沒有限制返回頭節點,僅僅是輸出的話,實現方式是N多種,陣列也是可以的。但是這裡的要求是返回反轉後的頭節點,所以,需要輔助手段。
//需要把節點儲存起來,斷開之後防止找不到
func reverseList(head *ListNode) *ListNode {
var prevHead *ListNode //儲存遍歷的前一個節點
var currentNode = head //儲存遍歷的當前節點
for currentNode != nil {
nextNode := currentNode.Next
currentNode.Next = prevHead
prevHead = currentNode
currentNode = nextNode
}
return prevHead
}
按照課本上的做法,我臨摹過來的程式碼,但是,這個程式碼我感覺有點小問題,卻又不知道問題在哪,各位朋友知道的還望指出來,並改正一下,不勝感激。
func reverseList(head *ListNode) *ListNode {
var reversedHead *ListNode //反轉後的頭節點
var prev *ListNode
var node = head
for node != nil {
next := node.Next
if next == nil {
reversedHead = node
}
node.Next = prev
prev = node
node = next
}
return reversedHead
}
面試題17: 合併兩個排序的連結串列
leetcode-cn.com/problems/he-bing-l...
同樣利用了連結串列的假頭和新連結串列作為輔助
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
dummy := &ListNode{}
tail := dummy
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
tail.Next = l1
l1 = l1.Next
} else {
tail.Next = l2
l2 = l2.Next
}
tail = tail.Next
}
if l1 != nil {// 如果l1還有,直接拼接到後面
tail.Next = l1
tail = tail.Next
l1 = l1.Next
}
if l2 != nil {//如果l2還有,直接拼接後面
tail.Next = l2
tail = tail.Next
l2 = l2.Next
}
return dummy.Next
}
面試題18: 樹的子結構
leetcode-cn.com/problems/shu-de-zi...
輸入兩棵二叉樹A和B,判斷B是不是A的子結構。(約定空樹不是任意一個樹的子結構)
主要和樹相關的,就沒有簡單級別的,當然,和遞迴相關的也都是沒有簡單的。不熟悉遞迴和樹的,這題需要點時間去理解。
func isSubStructure(A *TreeNode, B *TreeNode) bool {
var result = false
if A != nil && B != nil {
//先比較根節點,再比較左右子樹節點
if A.Val == B.Val {
//根節點滿足,判斷不是子結構
result = isStructure(A,B) //不用遞迴,也可寫成左右子樹的判斷
}
//比較左子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Left,B)
}
//比較右子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Right,B)
}
}
return result
}
func isStructure(a *TreeNode, b *TreeNode) bool {
if b == nil {//說明樹 B 已匹配完成(越過葉子節點),因此返回 true
return true
}
if a == nil {//說明已經越過樹 A 葉子節點,即匹配失敗,返回 false
return false
}
if a.Val != b.Val {//值不同,匹配失敗,返回 false
return false
}
return isStructure(a.Left,b.Left) && isStructure(a.Right,b.Right)
}
``````go
func isSubStructure(A *TreeNode, B *TreeNode) bool {
var result = false
if A != nil && B != nil {
//先比較根節點,再比較左右子樹節點
if A.Val == B.Val {
//根節點滿足,判斷不是子結構
result = isStructure(A,B) //不用遞迴,也可寫成左右子樹的判斷
}
//比較左子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Left,B)
}
//比較右子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Right,B)
}
}
return result
}
func isStructure(a *TreeNode, b *TreeNode) bool {
if b == nil {//說明樹 B 已匹配完成(越過葉子節點),因此返回 true
return true
}
if a == nil {//說明已經越過樹 A 葉子節點,即匹配失敗,返回 false
return false
}
if a.Val != b.Val {//值不同,匹配失敗,返回 false
return false
}
return isStructure(a.Left,b.Left) && isStructure(a.Right,b.Right)
}
``````go
func isSubStructure(A *TreeNode, B *TreeNode) bool {
var result = false
if A != nil && B != nil {
//先比較根節點,再比較左右子樹節點
if A.Val == B.Val {
//根節點滿足,判斷不是子結構
result = isStructure(A,B) //不用遞迴,也可寫成左右子樹的判斷
}
//比較左子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Left,B)
}
//比較右子樹為根節點,開始遍歷比較
if !result {
result = isSubStructure(A.Right,B)
}
}
return result
}
func isStructure(a *TreeNode, b *TreeNode) bool {
if b == nil {//說明樹 B 已匹配完成(越過葉子節點),因此返回 true
return true
}
if a == nil {//說明已經越過樹 A 葉子節點,即匹配失敗,返回 false
return false
}
if a.Val != b.Val {//值不同,匹配失敗,返回 false
return false
}
return isStructure(a.Left,b.Left) && isStructure(a.Right,b.Right)
}
接下來的演算法都是非常難的,我也不知道多久能研究透徹,可以確定的是更新時間肯定比第二第三章要晚。
本作品採用《CC 協議》,轉載必須註明作者和本文連結