iOS-算法集錦-劍指offer-百題詳解之一

目錄

閱前需知

1.本文部分內容參考劍指 offer 題解,如有侵權,請告知。其他內容均屬原創,轉載請告知。
2.本文示例代碼中給一些類增加了一些類擴展,因篇幅原因,未在文中寫出,詳情見項目源碼,地址文末有提供。
3.閱讀本文之前需要先了解節點,鏈表,,二叉樹的實現。詳情見如下文章連接。
4.因為 OC 中沒有棧,鏈表節點,鏈表的概念,所以本項目自定義了棧,鏈表節點,鏈表類。
5.因技術水平有限,如有錯誤,歡迎指正。

以下是通過 OC 語法實現

3.數組中重復的數字

題目描述

在一個長度為 n 的數組里的所有數字都在 0 到 n-1 的范圍內。數組中某些數字是重復的,但不知道有幾個數字是重復的,也不知道每個數字重復幾次。請找出數組中任意一個重復的數字。

Input: {2, 3, 1, 0, 2, 5}
Output:2
解題思路

要求復雜度為 O(N) + O(1),也就是時間復雜度 O(N),空間復雜度 O(1)。因此不能使用排序的方法,也不能使用額外的標記數組。??途W討論區這一題的首票答案使用 nums[i] + length 來將元素標記,這么做會有加法溢出問題。

這種數組元素在 [0, n-1] 范圍內的問題,可以將值為 i 的元素調整到第 i 個位置上。

以 (2, 3, 1, 0, 2, 5) 為例:

position-0 : (2,3,1,0,2,5) // 2 <-> 1
             (1,3,2,0,2,5) // 1 <-> 3
             (3,1,2,0,2,5) // 3 <-> 0
             (0,1,2,3,2,5) // already in position
position-1 : (0,1,2,3,2,5) // already in position
position-2 : (0,1,2,3,2,5) // already in position
position-3 : (0,1,2,3,2,5) // already in position
position-4 : (0,1,2,3,2,5) // nums[i] == nums[nums[i]], exit

遍歷到位置 4 時,該位置上的數為 2,但是第 2 個位置上已經有一個 2 的值了,因此可以知道 2 重復。

具體代碼如下:
+ (NSArray *)duplicate:(NSArray *)nums {
    if (nums == nil || nums.count == 0) {
        return nil;
    }
    
    NSMutableArray *numbers = [NSMutableArray arrayWithArray:nums];
    NSMutableArray *temp = [NSMutableArray array];
    
    for (int i = 0; i < numbers.count; i++) {
        while ([numbers[i] intValue] != i) {
            int number = [numbers[i] intValue];
            // 檢查 number 與第 number 位置上的值是否相等,如果相等,說明該 number 重復了,否則索引 i 和 number 兩者的值
            if (number == [numbers[number] intValue]) {
                [temp addObject:numbers[i]];
                return temp.copy;
            }
            [self cs_swap:numbers i:i j:number];
        }
    }
    
    return nil;
}

// 交換數組中i 和 j 位置上的數字
+ (void)cs_swap:(NSMutableArray *)numbers i:(int)i j:(int)j {
    if (i >= numbers.count || j >= numbers.count) {
        return;
    }
    NSNumber *number = numbers[i];
    numbers[i] = numbers[j];
    numbers[j] = number;
}
測試案例代碼
// 3.數組中重復的數字
- (void)repeatNumber {
    NSMutableArray *arrM = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        NSMutableArray *numbers = [NSMutableArray array];
        for (int j = 0; j < 10; j++) {
            [numbers addObject:[NSNumber numberWithInt:arc4random_uniform(10)]];
        }
        [arrM addObject:numbers];
    }
    
    for (int i = 0; i < arrM.count; i++) {
        NSArray *numbers = [arrM objectAtIndex:i];
        NSArray *repeatNumbers = [_3_repeatNumber duplicate:numbers];
        NSLog(@"i = %d, 原始數組:%@, 重復數字:%@",i,[numbers getAllObjectsDescription],[repeatNumbers getAllObjectsDescription]);
    }
}
運行結果
3.數組中重復的數字.png

4.二維數組中的查找

題目描述

在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

Consider the following matrix:
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

Given target = 5, return true.
Given target = 20, return false.
解題思路

從右上角開始查找。矩陣中的一個數,它左邊的數都比它小,下邊的數都比它大。因此,從右上角開始查找,就可以根據 target 和當前元素的大小關系來縮小查找區間。

復雜度:O(M + N) + O(1)

當前元素的查找區間為左下角的所有元素,例如元素 12 的查找區間如下:

image.png
詳細代碼如下
// 初始化一個二維數組
+ (bool)findNumber:(int)number numbers:(NSArray *)numbers {
    if (numbers == nil) {
        NSArray *number1 = @[@1,@4,@7,@11,@15];
        NSArray *number2 = @[@2,@5,@8,@12,@19];
        NSArray *number3 = @[@3,@6,@9,@16,@22];
        NSArray *number4 = @[@10,@13,@14,@17,@24];
        NSArray *number5 = @[@18,@21,@23,@26,@30];
        numbers = @[number1,number2,number3,number4,number5];
    }
    return [self find:number matrix:numbers];
}

// 在二維數組 matrix 中查找目標數 target
+ (bool)find:(int)target matrix:(NSArray *)matrix {
    if (matrix == nil || matrix.count == 0) {
        return false;
    }
    NSUInteger rows = matrix.count;    // 行數
    NSArray *colArray = matrix[0];
    NSUInteger cols = colArray.count;  // 列數
    int r = 0;  // 第 r 行
    int c = (int)cols - 1; // 第 c 列 從右上角開始
    
    while (r <= rows - 1 && c >= 0) {
        if (target == [matrix[r][c] integerValue]) {
            NSLog(@"target = %d, row = %d, col = %d",target,r,c);
            return true;
        } else if (target > [matrix[r][c] integerValue]) {
            r++; // 行數+1
        } else {
            c--;    // 列數減1
        }
    }
    return false;
}
測試案例代碼
// 4.二維數組中的查找
- (void)twoDimensionArrayFind {
    NSArray *targets = @[@16,@6,@30,@20,@15];
    for (int i = 0; i < targets.count; i++) {
        bool isFind = [TwoDimensionalArrayFind_04 findNumber: [targets vFI:i] numbers:nil];
        if (!isFind) {
            NSLog(@"target = %d, noFind",[targets vFI:i]);
        }
    }
}
運行結果
4.二維數組中的查找.png

5.替換空格

題目描述

將一個字符串中的空格替換成 "%20"。

Input:
"We Are Happy"

Output:
"We%20Are%20Happy"
解題思路

在字符串尾部填充任意字符,使得字符串的長度等于替換之后的長度。因為一個空格要替換成三個字符(%20),因此當遍歷到一個空格時,需要在尾部填充兩個任意字符。

令 P1 指向字符串原來的末尾位置,P2 指向字符串現在的末尾位置。P1 和 P2 從后向前遍歷,當 P1 遍歷到一個空格時,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否則就填充上 P1 指向字符的值。

從后向前遍是為了在改變 P2 所指向的內容時,不會影響到 P1 遍歷原來字符串的內容。

詳細代碼如下
+ (NSString *)replaceSpace:(NSString *)str {
    int p1 = (int)str.length - 1;
    NSMutableString *strM = [NSMutableString stringWithString:str];
    
    // 1.將在原來的空格后面再加上2個空格
    for (int i = 0; i < p1 + 1; i++) {
        NSString *temp = [str substringWithRange:NSMakeRange(i, 1)];
        if ([temp isEqualToString:@" "]) {
            [strM appendString:@"  "];
        }
    }
    
    // p1 p2依次從后往前遍歷
    int p2 = (int)strM.length - 1;
    while (p1 >= 0 && p2 >= 0) {
        NSString *temp = [strM substringWithRange:NSMakeRange(p1--, 1)];
        if ([temp isEqualToString:@" "]) {  // 發現空格 - 逆序填充02%
            [strM replaceCharactersInRange:NSMakeRange(p2--, 1) withString:@"0"];
            [strM replaceCharactersInRange:NSMakeRange(p2--, 1) withString:@"2"];
            [strM replaceCharactersInRange:NSMakeRange(p2--, 1) withString:@"%"];
        } else {
            [strM replaceCharactersInRange:NSMakeRange(p2--, 1) withString:temp];
        }
    }
    
    return strM.copy;
}
測試案例代碼
// 5.替換空格
- (void)replaceSpace {
    NSString *str = @"We Are Happy";
    NSLog(@"oldStr = %@",str);
    NSString *newStr = [ReplaceBlank_05 replaceSpace:str];
    NSLog(@"newStr = %@",newStr);
}
運行結果
5.替換空格.png

6.從尾到頭打印鏈表

本題使用到,鏈表節點,鏈表。項目中已經實現了這些類的定義,詳情請看原項目,這里不再過多的闡述。

題目描述

輸入鏈表的第一個節點,從尾到頭反過來打印出每個結點的值。


image.png
解題思路
6.1 使用棧
詳細代碼如下
/** 使用棧 */
+ (NSArray *)printListFromTailToHeadByShed:(NSArray *)numbers {
    LinkedArray *linkedArray = [[LinkedArray alloc] initLiknedArrayWithNunbers:numbers];
    // 第一個節點
    ListNode *listNode = [linkedArray getFirstListNode];
    
    return [self getListFromTailToHead:listNode];
}

// 使用棧
+ (NSArray *)getListFromTailToHead:(ListNode *)listNode {
    // 創建一個棧
    Stack *stack = [[Stack alloc] init];
    
    // 開始從第一個節點依次往后遍歷,將數據全部入棧
    while (listNode != nil) {
        [stack push:listNode.content];
        listNode = listNode.next;
    }
    
    NSMutableArray *values = [NSMutableArray array];
    // 依次將棧出列并存儲
    while (!stack.isEmpty) {
        [values addObject:stack.popObj];
    }
    
    return values.copy;
}
測試案例代碼
// 1.使用棧
NSArray *arr = [PrintListFromTailToHead_06 printListFromTailToHeadByShed:@[@1,@2,@3]];
NSLog(@"arr = %@",arr);
運行結果
6.1使用棧.png
6.2 使用遞歸
詳細代碼如下
/** 使用遞歸 */
+ (NSArray *)printListFromTailToHeadByRecursion:(NSArray *)numbers {
    LinkedArray *linkedArray = [[LinkedArray alloc] initLiknedArrayWithNunbers:numbers];
    // 第一個節點
    ListNode *listNode = [linkedArray getFirstListNode];
    
    NSMutableArray *values = [NSMutableArray array];
    if (listNode != nil) {
        [values addObjectsFromArray:[self getListFromTailToHead:listNode.next]];
        [values addObject:listNode.content];
    }
    
    return values;
}

// 使用棧
+ (NSArray *)getListFromTailToHead:(ListNode *)listNode {
    // 創建一個棧
    Stack *stack = [[Stack alloc] init];
    
    // 開始從第一個節點依次往后遍歷,將數據全部入棧
    while (listNode != nil) {
        [stack push:listNode.content];
        listNode = listNode.next;
    }
    
    NSMutableArray *values = [NSMutableArray array];
    // 依次將棧出列并存儲
    while (!stack.isEmpty) {
        [values addObject:stack.popObj];
    }
    
    return values.copy;
}
測試案例代碼
// 2.使用遞歸
NSArray *arr = [PrintListFromTailToHead_06 printListFromTailToHeadByRecursion:@[@1,@2,@3]];
NSLog(@"arr = %@",arr);
運行結果
6.2使用遞歸.png
6.3 使用頭插法
解題思路

利用鏈表頭插法為逆序的特點。

頭結點和第一個節點的區別:

  • 頭結點是在頭插法中使用的一個額外節點,這個節點不存儲值;
  • 第一個節點就是鏈表的第一個真正存儲值的節點。
詳細代碼如下
+ (NSArray *)printListFromTailToHeadByInsert:(NSArray *)numbers {
    LinkedArray *linkedArray = [[LinkedArray alloc] initLiknedArrayWithNunbers:numbers];
    // 第一個節點
    ListNode *listNode = [linkedArray getFirstListNode];
    
    // 頭插法構建逆序鏈表
    ListNode *head = [[ListNode alloc] init];
    while (listNode != nil) {
        ListNode *memo = listNode.next;
        listNode.next = head.next;
        head.next = listNode;
        listNode = memo;
    }
    // 構建 ArrayList
    NSMutableArray *values = [NSMutableArray array];
    head = head.next;
    while (head != nil) {
        [values addObject:head.content];
        head = head.next;
    }
    
    return values;
}
測試案例代碼
// 3.使用頭表插入法
NSArray *arr = [PrintListFromTailToHead_06 printListFromTailToHeadByInsert:@[@1,@2,@3]];
NSLog(@"arr = %@",arr);
運行結果
6.3頭插入法.png
6.4 使用 Collections.reverse()
詳細代碼如下
/** 使用Collection reverse*/
+ (NSArray *)printListFromTailToHeadByReverse:(NSArray *)numbers {
    LinkedArray *linkedArray = [[LinkedArray alloc] initLiknedArrayWithNunbers:numbers];
    // 第一個節點
    ListNode *listNode = [linkedArray getFirstListNode];
    
    // 構建 ArrayList
    NSMutableArray *values = [NSMutableArray array];
    while (listNode != nil) {
        [values addObject:listNode.content];
        listNode = listNode.next;
    }
    
    NSArray *newArr = [[values reverseObjectEnumerator] allObjects];
    
    return newArr;
}
測試案例代碼
// 4.使用reverse 反序
NSArray *arr = [PrintListFromTailToHead_06 printListFromTailToHeadByReverse:@[@1,@2,@3]];
NSLog(@"arr = %@",arr);
運行結果
6.4使用反序.png

7重建二叉樹

題目描述

根據二叉樹的前序遍歷和中序遍歷的結果,重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。

preorder = [3,9,20,15,7]
inorder =  [9,3,15,20,7]
image.png
解題思路

前序遍歷的第一個值為根節點的值,使用這個值將中序遍歷結果分成兩部分,左部分為樹的左子樹中序遍歷結果,右部分為樹的右子樹中序遍歷的結果。

詳細代碼如下
+ (BinaryTreeNode *)reConstructBinaryTree:(NSArray *)preorders inorders:(NSArray *)inorders {
    // 初始化哈希表
    HashMap *indexForInOrders = [[HashMap alloc] init];
    
    for (int i = 0; i < inorders.count; i++) {
        [indexForInOrders put:[inorders[i] intValue] index:i];
    }
    
    return [self reConstructBinaryTreeWithOrders:indexForInOrders preorders:preorders preL:0 preR:(int)preorders.count - 1 inL:0];
}

+ (BinaryTreeNode *)reConstructBinaryTreeWithOrders:(HashMap *)indexForInOrders preorders:(NSArray *)preorders preL:(int)preL preR:(int)preR inL:(int)inL {
    if (preL > preR) {
        return nil;
    }
    
    // 取左邊二叉樹的值構成一個新的節點
    BinaryTreeNode *root = [BinaryTreeNode binaryTreeNodeWithValue:[preorders[preL] integerValue]];
    int inIndex = [indexForInOrders get:(int)root.value];
    int leftTreeSize = inIndex - inL;
    
    root.leftNode = [self reConstructBinaryTreeWithOrders:indexForInOrders preorders:preorders preL:preL + 1 preR:preL + leftTreeSize inL:inL];
    root.rightNode = [self reConstructBinaryTreeWithOrders:indexForInOrders preorders:preorders preL:preL + leftTreeSize + 1 preR:preR inL:inL + leftTreeSize + 1];
    
    return root;
}
測試案例代碼
// 7.重建二叉樹
- (void)reConstructBinaryTree {
    NSArray *preorders = @[@3,@9,@20,@15,@7];
    NSArray *inorders = @[@9,@3,@15,@20,@7];
    BinaryTreeNode *treeNode = [ReConstructBinaryTree_07 reConstructBinaryTree:preorders inorders:inorders];
    NSLog(@"treeNode = %@",treeNode);
}
運行結果
image.png

8. 二叉樹的下一個結點

題目描述

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點并且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

解題思路
  • 如果一個節點的右子樹不為空,那么該節點的下一個節點是右子樹的最左節點;
image.png
  • 否則,向上找第一個左鏈接指向的樹包含該節點的祖先節點。
image.png
詳細代碼如下
測試案例代碼
運行結果

9.用兩個棧實現隊列

題目描述

用兩個棧來實現一個隊列,完成隊列的 Push 和 Pop 操作。

解題思路

in 棧用來處理入棧(push)操作,out 棧用來處理出棧(pop)操作。一個元素進入 in 棧之后,出棧的順序被反轉。當元素要出棧時,需要先進入 out 棧,此時元素出棧順序再一次被反轉,因此出棧順序就和最開始入棧順序是相同的,先進入的元素先退出,這就是隊列的順序。

image.png
詳細代碼如下
+ (NSNumber *)twoStackToQueue:(NSArray *)numbers {
    Stack *inStack = [[Stack alloc] init];
    Stack *outStack = [[Stack alloc] init];
    
    // 先將所有元素入棧
    for (NSNumber *number in numbers) {
        [inStack push:number];
    }
    
    if (outStack.isEmpty) {
        while (!inStack.isEmpty) {
            [outStack push:inStack.popObj];
        }
    }
    
    if (outStack.isEmpty) {
        NSLog(@"queue is empty");
    }
    
    return outStack.popObj;
}
測試案例代碼
// 9.用兩個棧實現隊列
- (void)twoStackToQueue {
    NSArray *numbers = @[@1,@2,@3,@4];
    NSNumber *lastNumber = [TwoStackQueue twoStackToQueue:numbers];
    NSLog(@"lastNumber = %@",lastNumber);
}
運行結果
image.png

10.1 斐波那契數列

題目描述

求斐波那契數列的第 n 項,n <= 39。

image.png

如果使用遞歸求解,會重復計算一些子問題。例如,計算 f(10) 需要計算 f(9) 和 f(8),計算 f(9) 需要計算 f(8) 和 f(7),可以看到 f(8) 被重復計算了。

image.png
解題思路

考慮到第 i 項只與第 i-1 和第 i-2 項有關,因此只需要存儲前兩項的值就能求解第 i 項,從而將空間復雜度由 O(N) 降低為 O(1)。

詳細代碼如下
+ (int)Fibonacci:(int)number {
    if (number <= 1) {
        return number;
    }
    
    int pre2 = 0, pre1 = 1, fib = 0;
    
    for (int i = 2; i < number + 1; i++) {
        fib = pre1 + pre2;
        pre2 = pre1;
        pre1 = fib;
    }
    
    return fib;
}
測試案例代碼
// 10.1 斐波那契數列
- (void)fibonacci {
    for (int i = 0; i < 20; i++) {
        NSLog(@"i = %d, total = %d",i,[Fibonacci Fibonacci:i]);
    }
}
運行結果
10.1斐波那契數列.png

10.2 跳臺階

題目描述

一只青蛙一次可以跳上 1 級臺階,也可以跳上 2 級。求該青蛙跳上一個 n 級的臺階總共有多少種跳法。

解題思路

參考上一題思路

詳細代碼如下
+ (int)jumpFloor:(int)number {
    if (number <= 2) {
        return number;
    }
    
    int pre1 = 2, pre2 = 1;
    int result = 1;
    
    for (int i = 2; i < number; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    }
    
    return result;
}
測試案例代碼
// 10.2 跳臺階
- (void)jumpFloor {
    for (int i = 0; i < 10; i++) {
        NSLog(@"i = %d, total = %d",i,[Fibonacci jumpFloor:i]);
    }
}
運行結果
10.2跳臺階.png

10.3 變態跳臺階

題目描述

一只青蛙一次可以跳上 1 級臺階,也可以跳上 2 級... 它也可以跳上 n 級。求該青蛙跳上一個 n 級的臺階總共有多少種跳法。

解題思路

參考10.1實現思路

詳細代碼如下
+ (int)jumpFloor2:(int)number {
    if (number <= 0) {
        return 0;
    }
    // 1.構建數組
    NSMutableArray *arrM = [NSMutableArray array];
    for (int i = 0; i < number; i++) {
        [arrM addObject:[NSNumber numberWithInt:1]];
    }
    
    // 2.
    for (int i = 1; i < number; i++) {
        for (int j = 0; j < i; j++) {
            arrM[i] = [NSNumber numberWithInt:[arrM[i] intValue] + [arrM[j] intValue]];
        }
    }
    
    return [arrM[number - 1] intValue];
}
測試案例代碼
// 10.3 變態跳臺階
- (void)jumpFloorII {
    for (int i = 0; i < 10; i++) {
        NSLog(@"i = %d, total = %d",i,[Fibonacci jumpFloor2:i]);
    }
}
運行結果
10.3變態跳臺階.png

10.4 矩形覆蓋

題目描述

我們可以用 21 的小矩形橫著或者豎著去覆蓋更大的矩形。請問用 n 個 21 的小矩形無重疊地覆蓋一個 2*n 的大矩形,總共有多少種方法?

解題思路

參考10.1實現思路

詳細代碼如下
+ (int)rectCover:(int)number {
    if (number <= 2) {
        return number;
    }
    
    int pre1 = 2, pre2 = 1;
    int result = 0;
    
    for (int i = 3; i < number + 1; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    }
    
    return result;
}
測試案例代碼
// 10.4 矩形覆蓋
- (void)rectCover {
    for (int i = 0; i < 10; i++) {
        NSLog(@"i = %d, total = %d",i,[Fibonacci rectCover:i]);
    }
}
運行結果
10.4矩形覆蓋.png

本文參考CS_Nodes 劍指 offer 題解.md 非常感謝該作者


相關知識點文章參考鏈接地址

  • 更多OC算法詳解文章請持續關注作者,本人會在接下來的時間陸續整理。

如有錯誤,歡迎指正,多多點贊,打賞更佳,您的支持是我寫作的動力。


項目連接地址 - ArithmeticDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。