swift 刷算法笔记 (二) - 数组,链表,哈希表,字符串,指针

80 阅读6分钟

算法总结:

判断是否是异位词 , 字幕出现的概率相同

  • 数组 
  • 链表 
  • 哈希表 
  • 字符串 
  • 双指针法 

链表

题目来源于

代码随想录链接

class Solution {
  func isAnagram(_ s: String, _ t: String) -> Bool {
          if s.count != t.count {
             return false
          }
           var setS : Dictionary<String,Int> = [:]
           var setT : Dictionary<String,Int> = [:]


           var arrS = Array(s)
           var arrT = Array(t)

           for i in 0..<arrS.count {
               var  s1 = String(arrS[i])
               var  t1 = String(arrT[i])
               var key1:Int = setT[s1] ?? 0
               var key2:Int = setS[t1] ?? 0
               setT[s1] = Int(key1) + 1
               setS[t1] = Int(key2) + 1
           }
           for i in 0..<arrS.count {
               var  s1 = String(arrS[i])
               var key1 = setT[s1] ?? 0
               var key2 = setS[s1] ?? 0

            if key1 != key2 {
                  return false
            }

           }
          return true
      }
}

算法2)

class Solution {
    func isAnagram(_ s: String, _ t: String) -> Bool {
       if s.count != t.count {
           return false
       }
       //错误1 字典: 
       var dicS: Dictionary<String , Int> = [:]

       var arrS = Array(s)
       var arrT = Array(t)

      // 字符串中的都存起来 
       for i in arrS {
       // 错误2 i没有String 强转 
           var value = dicS[String(i)] ?? 0 
           dicS[String(i)]  = Int(value) + 1
       } 

        for i in arrT {
           var value = dicS[String(i)] ?? 0 
           dicS[String(i)]  = Int(value) - 1
       } 
        // 错误3  values 和keys 不熟悉 
       var valueArr = dicS.values
       for i in valueArr {
           if i != 0 {
               return false
           }
       }

       return true 

    }
}

两两交换链表的节点

力扣题目链接(opens new window)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

24.两两交换链表中的节点-题意

原题链接

《代码随想录》算法公开课:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点 (opens new window),相信结合视频在看本篇题解,更有助于大家对链表的理解。

这道题目正常模拟就可以了。

建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

对虚拟头结点的操作,还不熟悉的话,可以看这篇链表:听说用虚拟头节点会方便很多? (opens new window)

接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序

初始时,cur指向虚拟头结点,然后进行如下三步:

24.两两交换链表中的节点1

操作之后,链表如下:

24.两两交换链表中的节点2

看这个可能就更直观一些了:

24.两两交换链表中的节点3

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     public var val: Int
 *     public var next: ListNode?
 *     public init() { self.val = 0; self.next = nil; }
 *     public init(_ val: Int) { self.val = val; self.next = nil; }
 *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
 * }
 */
class Solution {
    func swapPairs(_ head: ListNode?) -> ListNode? {
    // 错误1 . 前面没加问号 
        if(head == nil || head?.next == nil) {
            return head
        }
         var dummyNode = ListNode(0)
         dummyNode.next = head
         var temp:ListNode? = dummyNode
         while temp?.next != nil  && temp?.next?.next != nil   {
             var node1:ListNode? = temp?.next
             var node2: ListNode? = temp?.next?.next
              // 错误2 . 前面没加问号
             // temp -> node1 -> node2
             // temp -> node2 -> node1
             temp?.next = node2
             node1?.next = node2?.next
             node2?.next = node1
 // 错误3 . 前面没加问号
             temp = node1

         }
         return dummyNode.next
    }
}

链表中的第K个节点


class Solution {
    func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
       //判空 
       if head == nil {
           return head
       }

       if n == 0 {
           return head
       }
       var dummyNode = ListNode(0)
       dummyNode.next = head

       var fast:ListNode? = dummyNode
       var slow: ListNode? = dummyNode
       // 向后移动N步
       for i in 0..<n {
         fast = fast?.next
       }
 // 错误3 . 前面没加问号

       // 同时后移 
       while fast?.next != nil {
            fast = fast?.next
            slow =  slow?.next 
       }
    
        // 执行删除操作 
        slow?.next = slow?.next?.next
  
       // 最后一个返回 .
       return dummyNode.next
    }
}

判断数组的交集

class Solution {
    func intersection(_ nums1: [Int], _ nums2: [Int]) -> [Int] {
    // 错误1 数组没给类型 
        var arr:[Int] = []
        if nums1.count == 0 || nums2.count == 0  {
           return arr
        }

        var set1:Set<Int> = []
        var set2:Set<Int> = []

        for i in nums1 {
            set1.insert(i)
        }
        
        for i in nums2 {
            set2.insert(i)
        }
        for i in set1 {
        // 错误2 数组没给 contains 不对 
           if set2.contains(i) {
               arr.append(i)
           }
            //  for j in set2 {
            //      if  j == i {
            //           arr.append(i)
            //      }
            //  }
        }
        return arr 

    }
}

计算两位数的和的小算法

 func isNext(_ n: Int) -> Int {
        var sum = 0 
        var  n = n 
        while n != 0 {
            n1 = n % 10 //得到各位 
            sum = sum + n1 * n1 // 加各位 
            n = n / 10 // 得到更高位 
        }
        return sum 
    }

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

原题链接

class Solution {
    func twoSum(_ nums: [Int], _ target: Int) -> [Int] {

       var dic:Dictionary<Int,Int> = [:]
       for i in 0..<nums.count  {
          let value = dic[target - nums[i]]
          if value != nil {
             return [i , value!]
          } else {
              dic[nums[i]] = i // 添加时机很重要 , 
          }
       }
       return  []
    }
}

四数之和

力扣题目链接(opens new window)

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

例如:

输入:

  • A = [ 1, 2]
  • B = [-2,-1]
  • C = [-1, 2]
  • D = [ 0, 2]

输出:

2

解释:

两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

原题链接

class Solution {
    func fourSumCount(_ nums1: [Int], _ nums2: [Int], _ nums3: [Int], _ nums4: [Int]) -> Int {
        var count = 0 
        var dic: Dictionary<Int,Int> = [:]
        for i in 0..<nums1.count {
            for j in 0..<nums2.count {
                let sum = nums1[i] + nums2[j]
                // 错误1 没有判空 
                if dic[sum] != nil {
                   dic[sum] =  dic[sum]!  + 1
                } else {
                    dic[sum] = 1 
                }
            } 
            // 错误2 打印 
        }
   // 错误4 下标遍历不对 
         for i in 0..<nums3.count {
            for j in 0..<nums4.count {
                let sum = nums3[i] + nums4[j] 
                if dic[-sum] != nil {
                    count += dic[-sum]!
                    // 错误3 漏洞 将count 清0  
                }
            }
        }
        return count
    }
}

字典核心算法

result += countDic[key, default: 0]

三数之和

力扣题目链接(opens new window)

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:  答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

原题链接

class Solution {
    func threeSum(_ nums: [Int]) -> [[Int]] {
        var nums = nums
        if nums.count  < 3 {
            return []
        }
        var res:[[Int]] = []
        // 错误1 排序不会 
        nums =   nums.sorted(by : < )
        print(nums)
        // 对数组遍历 
        for i in 0..<nums.count {
           if nums[i] > 0 {
               return res
           }
           if i >= 1 &&  nums[i] == nums[i-1] {
               continue
           } 
           var l = i + 1 
           var r = nums.count - 1 
           while l < r {
               if nums[i] + nums[l] + nums[r] == 0 {       // 错误2 
                   if l - 1 > i && nums[l] == nums[l - 1] {
                       l = l + 1
                       continue
                   }

                  res.append([nums[i],nums[l],nums[r]])
                  l += 1
               } else if nums[i] + nums[l] + nums[r]  > 0 {
                   r -= 1
               } else if nums[i] + nums[l] + nums[r]  < 0 {
                   l += 1
               }
           }
        }
        return res

    }
}

字符串

反转字符串 - 双指针

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

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

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

示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

    class Solution {
        func reverseString(_ s: inout [Character]) {
           var l = 0
           var r = s.count - 1
           while l < r {
            //    (s[l] , s[r]) = (s[r],s[l])
            //   let temp = s[l]
            //   s[l] = s[r]
            //   s[r] = temp
            // swap(s[l],s[r])
            s.swapAt(l, r)

               l += 1
               r -= 1
           }
        }
    }

反转字符串

leetcode.cn/problems/re…

给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。

如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例:

输入: s = "abcdefg", k = 2
输出: "bacdfeg"

原题链接

class Solution {
    func reverseStr(_ s: String, _ k: Int) -> String {
        // 错误1  数组类型 
       var arr:[String.Element] = Array(s)
       var l = 0 
       var r =  arr.count - 1
       while l < r {
           reverse(&arr,l,min(l + k - 1,r) )
           l = l + 2*k 
       }
       // 错误2 数组转字符串 
       return  String(arr)
    }

    // 交换范围内的字符串 
    func reverse(_ arr: inout [String.Element], _ l : Int , _ r: Int) {
       var l = l 
       var r = r 
       while l < r {
           arr.swapAt(l,r)
           l += 1
           r -= 1
       }
    }
}

反转链表

class Solution {
    func reverseList(_ head: ListNode?) -> ListNode? {
        var cur = head
        var pre:ListNode? = nil 
        
        pre -> cur  ->  cur->next (temp)
        pre <- cur  <-  cur->next (temp) 

        
        
        while cur != nil {
            var temp = cur!.next // 暂存下一个 
            cur!.next = pre // 修改指针
            pre  = cur // 前置指针后移 
            cur = temp // 当前后移 
        }
        return pre 

}

#@ 替换空格 - 双指针

如果想把这道题目做到极致,就不要只用额外的辅助空间了!

首先扩充数组到每个空格替换成"%20"之后的大小。

然后从后向前替换空格,也就是双指针法,过程如下:

i指向新长度的末尾,j指向旧长度的末尾。

替换空格

相关的题目

class Solution {
    func replaceSpace(_ s: String) -> String {
       // 计算空格的个数 
       var arr: [String.Element] = Array(s)
       // 指向原来数组的最右边 
       var left = arr.count - 1 
       var count = 0
       for i in 0..<arr.count {
          let ch = arr[i]
          if ch == " " {
            count += 1 
          }
       }
       print("arr count = \(count)")

       // 数组扩容 
       for i in 0..<count {
           arr.append(" ")
           // 错误1 漏加这里 少了一个添加 
           arr.append(" ")

       }
       // 指向新数组的最右边
       var right = arr.count - 1

       print("right  = \(right)")

       // 错误2 这里少了个0 
       // 两个指针 
       while left >= 0 {
           if arr[left] != " " {
              arr[right] = arr[left]
              left -= 1
              right -= 1
           } else {
              // 错误3: String.Element 不能是"20%" 必须是"0" "2" "%"
             // 进行移位 
              arr[right] = "0"
              arr[right-1] = "2"
              arr[right-2] = "%"

              right -= 3
              left -= 1
           }  
       }
       print("arr  = \(arr)")

       return String(arr)
    }
}

反转字符串中的单词

leetcode.cn/problems/re…

力扣题目链接(opens new window)

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:
输入: "the sky is blue"
输出: "blue is sky the"

示例 2:
输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:
输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

原题链接


class Solution {
func reverseWords(\_ s: String) -> String {
// 移除空格
var arr = Array(s)
var left  = 0
var right = arr.count - 1
var deque:\[String] = \[]

      // 去掉左右的空格 
      while arr[left] == " " {
          left += 1
      }
       while arr[right] == " " {
          right -= 1
      }
      var word = ""

      
      // 添加到队列中 
      while left <= right {
          if word.count > 0 &&  arr[left] == " " {
            deque.insert(word,at:0)
            word = ""
          } else if arr[left] != " "{
              word.append(arr[left])
          }
          left += 1
      }
      
      // 队列转化下 
      deque.insert(word, at:0) 
       var result = ""
       for ch in  0..<deque.count {
         result.append(deque[ch])
         if ch < deque.count - 1 {
             result.append(" ")
         }
       }
       return result 

    }

}

字符串技巧

typealias Element = Character
String.Element == Character 

最旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"

示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:
1 <= k < s.length <= 10000

原题链接


class Solution {
func reverseLeftWords(\_ s: String, \_ n: Int) -> String {
var mid = s.index(s.startIndex, offsetBy:n)
return String(s\[mid..\<s.endIndex]) + String(s\[s.startIndex..\<mid])

    }

}

参考文献

代码随想录