目錄
- 1. 前言
- 2. 實現 Singleton
- 3. 數組中重復的數字
- 4. 二維數組中的查找
- 5. 替換空格
- 6. 從尾到頭打印鏈表
- 7. 重建二叉樹
- 8. 二叉樹的下一個結點
- 9. 用兩個棧實現隊列
- 10.1 斐波那契數列
- 10.2 跳臺階
- 10.3 矩形覆蓋
- 10.4 變態跳臺階
閱前需知
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]);
}
}
運行結果
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 的查找區間如下:
詳細代碼如下
// 初始化一個二維數組
+ (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]);
}
}
}
運行結果
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);
}
運行結果
6.從尾到頭打印鏈表
本題使用到
棧
,鏈表節點
,鏈表
。項目中已經實現了這些類的定義,詳情請看原項目,這里不再過多的闡述。
題目描述
輸入鏈表的第一個節點,從尾到頭反過來打印出每個結點的值。
解題思路
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.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.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.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);
運行結果
7重建二叉樹
題目描述
根據二叉樹的前序遍歷和中序遍歷的結果,重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。
preorder = [3,9,20,15,7]
inorder = [9,3,15,20,7]
解題思路
前序遍歷的第一個值為根節點的值,使用這個值將中序遍歷結果分成兩部分,左部分為樹的左子樹中序遍歷結果,右部分為樹的右子樹中序遍歷的結果。
詳細代碼如下
+ (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);
}
運行結果
8. 二叉樹的下一個結點
題目描述
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點并且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。
解題思路
- 如果一個節點的右子樹不為空,那么該節點的下一個節點是右子樹的最左節點;
- 否則,向上找第一個左鏈接指向的樹包含該節點的祖先節點。
詳細代碼如下
測試案例代碼
運行結果
9.用兩個棧實現隊列
題目描述
用兩個棧來實現一個隊列,完成隊列的 Push 和 Pop 操作。
解題思路
in 棧用來處理入棧(push)操作,out 棧用來處理出棧(pop)操作。一個元素進入 in 棧之后,出棧的順序被反轉。當元素要出棧時,需要先進入 out 棧,此時元素出棧順序再一次被反轉,因此出棧順序就和最開始入棧順序是相同的,先進入的元素先退出,這就是隊列的順序。
詳細代碼如下
+ (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);
}
運行結果
10.1 斐波那契數列
題目描述
求斐波那契數列的第 n 項,n <= 39。
如果使用遞歸求解,會重復計算一些子問題。例如,計算 f(10) 需要計算 f(9) 和 f(8),計算 f(9) 需要計算 f(8) 和 f(7),可以看到 f(8) 被重復計算了。
解題思路
考慮到第 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.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.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.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]);
}
}
運行結果
本文參考CS_Nodes 劍指 offer 題解.md 非常感謝該作者
相關知識點文章參考鏈接地址
- 更多OC算法詳解文章請持續關注作者,本人會在接下來的時間陸續整理。
如有錯誤,歡迎指正,多多點贊,打賞更佳,您的支持是我寫作的動力。