剑指offer练习
写一个程序要注意:
1. 功能性测试(完成基本功能)
2. 边界值测试(单独处理边界)
3. 负面测试(处理无效数据)
其余的还有:
1. 变量命名规范化,可读化
2. 释放一个内存时,要记得把引用这块内存的指针置为NULL
通知函数的调用者函数出错有三种方式,比如第16题涉及到如果对0求负数幂,由于不能对0求倒数,应该提示调用出错:
1. 返回值
2. 全局变量
3. 抛出异常
基本概念:
1. 鲁棒性(健壮性)指程序能够判断输入是否合乎规范要求,并对不符合要求的输入予以合理的处理。
1. 赋值运算符
}
{
if(this == &s) return this;
char* temp = new char[s.size + 1];
if(temp){ // 如果分配成功了
delete m_pData;
m_pData = temp;
size = s.size;
}
return *this;
}
CMyString& operator = (const CMyString& s)
{
if(this != &s)
{
CMyString temp = CMyString(s);
// 交换自身的数据指针和临时对象的数据指针
char* tp = s.m_pData;
s.m_pData = m_pData;
m_pData = tp;
} // 出了if块之后,会调用temp的析构,释放它的内存
return *this;
}
2. 单例模式
3. 找数组中重复的数字
using namespace std;
#define SIZE 10
int hashTable[SIZE] = {0};
int main()
{
int num[SIZE] = {1, 2, 2, 3, 4, 5, 5 ,6, 7};
int repNum = -1;
for (int i = 0; i < SIZE; i++)
{
if (hashTable[num[i]] != 0)
{
repNum = num[i];
break;
}
hashTable[num[i]] ++;
}
cout << repNum;
}
using namespace std;
#define SIZE 10
int main()
{
int n[SIZE] = { 1, 2, 3, 4, 2, 5, 5 ,6, 7 };
#define swap(x, y)\
do {\
int t = x;\
x = y;\
y = t;\
}while(0)
int res = -1;
for (int i = 0; i < SIZE; )
{
if (n[i] != i)
{
if (n[n[i]] == n[i])
{
res = n[i];
break;
}
// 把数字放到它应在的位置
int v = n[i];
// 注意这里不能n[n[i]],一旦n[i]改了,结果就不对了
swap(n[i], n[v]);
}
}
cout << res;
}
4. 二维数组中的查找
问题描述:有一个二维数组,每行都按照从左到右递增,每一列按照从上到下递增。写一个函数,接受一个二维数组和一个整数,判断二维数组中是否有该整数。
比如有一个二维数组:
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
查找7, 5...
解决方案:从右上角(比如9)开始缩小查找区域,如果右上角的值大于目标值,则该列不可能有目标值,剔除一列,接着判断左边一列。如果某列第一个值小于目标值,则目标值有可能在此列,且不可能在此行,剔除此行(因为更左边更不可能有了,而右边的已经剔除了),规模立马缩小了。接着就是重复这个步骤,知道找到目标值,或者列到底,或者行到头,表示没有这个值。也就是说,每次我们只会去那右上角和目标值比较,不会去遍历整个表,或者某行某列。
using namespace std;
#define SIZE 4
bool find(int n[][SIZE], int num, int i, int j)
{
if (i > SIZE || j < 0) // 递归终点
return false;
if (n[i][j] > num)
return find(n, num, i, j - 1);
else if (n[i][j] == num)
return true;
else
return find(n, num, i + 1, j);
}
int main()
{
int n[][SIZE] = {
{1, 2, 8, 9},
{2, 4, 9, 12},
{4, 7, 10, 13},
{6, 8, 11, 15},
};
if (find(n, 14, 0, 3))
{
cout << "find number " << endl;
}
else
{
cout << "didnt find number" << endl;
}
}
5. 替换空格
using namespace std;
#define SIZE 1024 // buffer size
int main()
{
char strbuf[SIZE] = "We are happy.";
//cout << sizeof(strbuf); // 1024
int originalLen = strlen(strbuf) + 1; // 13 + 1, 包含结尾0
int newlen = originalLen;
for (int i = 0; strbuf[i] != 0; i++)
{
if (strbuf[i] == ' ')
newlen += 2;
}
if (newlen > SIZE)
return 0;
int pre = originalLen - 1;
int beh = newlen - 1;
while (pre >= 0)
{
if (strbuf[pre] == ' ')
{// 替换
strbuf[beh--] = '0';
strbuf[beh--] = '2';
strbuf[beh--] = '%';
}
else
{// 直接覆盖
strbuf[beh--] = strbuf[pre];
}
--pre;
}
cout << strbuf;
return 0;
}
6. 从尾到头打印链表
{
if(root == NULL) return ;
PrintListReversingly(root->m_pNext);
cout << root->m_pData << endl;
}
{
if(root == NULL) return ;
stack<ListNode*> nodes;
while(root)
{
nodes.push(root);
root = root->next;
}
while(!nodes.empty())
{
ListNode* p = nodes.top();
nodes.pop();
cout << p->m_pData;
}
}
7. 重建二叉树
Node* left;
using namespace std;
struct Node
{
int value;
Node* right;
Node* left;
};
//
Node* rebuild(int dlr[], int dlr_p, int s,// 前序遍历和区间, dir_p表示前序遍历中的子序列索引,s表示中序和前序序列的大小
int ldr[], int ldr_l, int ldr_r) // 中序遍历和区间, ldr_l,ldr_r分别表示中序遍历中子树的范围
{
// 边界
if (dlr_p < 0 || dlr_p >= s) return NULL;
if (ldr_l > ldr_r) return NULL;
// 判断
Node* node = new Node;
node->value = dlr[dlr_p]; // 前序遍历的第一个点就是根结点
// 从中序遍历中寻找根结点的位置
int pos = -1;
for (int i = ldr_l; i <= ldr_r; i++)
{
if (ldr[i] == node->value)
{
pos = i;
break;
}
}
int lsize = pos - ldr_l; // 左子树宽度
int rsize = ldr_r - pos; // 右子树宽度
node->left = rebuild(dlr, dlr_p + 1, s, ldr, ldr_l, pos - 1);
node->right = rebuild(dlr, dlr_p + lsize + 1, s, ldr, pos + 1, ldr_r);
return node;
}
// 后续遍历
void lrd(Node* root)
{
if (root == NULL)return;
lrd(root->left);
lrd(root->right);
cout << root->value << endl;
}
int main()
{
const int size = 8;
int dlr[size] = {1, 2, 4, 7, 3, 5, 6, 8};
int ldr[size] = {4, 7, 2, 1, 5, 3, 8, 6};
Node* root = rebuild(dlr, 0, size, ldr, 0, size - 1);
lrd(root);
}
8. 二叉树的下一个节点
{
if(node == NULL) return NULL;
Node* parent = node->parent;
if(parent->left == node) return parent;
else
{// 是右节点
Node* next = node->right ;
if(next)
{//,从右子树中找最左节点,一直遍历left,知道->left为空
while(next->left)
next = next->left;
}
else
{
next = findNext(node->parent);
}
return next;
}
}
9. 用两个栈实现一个队列、用两个队列实现一个栈

#include <stack>
using namespace std;
// MyQueue适配器
class MyQueue
{
public:
MyQueue():s1(), s2()
{
}
stack<int> s1;
stack<int>s2;
void push(int n)
{
s1.push(n);
}
void pop()
{
if (!s2.empty())
{
s2.pop();
return;
}
while (!s1.empty())
{
int n = s1.top();
s2.push(n);
s1.pop();
}
s2.pop();
}
int front()
{
if (!s2.empty())
{
return s2.top();
}
while (!s1.empty())
{
int n = s1.top();
s2.push(n);
s1.pop();
}
return s2.top();
}
bool empty()
{
return s1.empty() && s2.empty();
}
};
int main()
{
MyQueue q;
q.push(1);
q.push(3);
q.push(2);
q.push(5);
q.push(4);
cout << q.front() << endl;
q.pop();
cout << q.front() << endl;
while (!q.empty())
{
cout << q.front();
q.pop();
}
}
#include <iostream>
#include <queue>
using namespace std;
class MyStack
{
public:
MyStack() :q1(), q2()
{
}
// 每次只会有一个队列非空
queue<int> q1;
queue<int> q2;
void push(int n)
{
// 当前操纵队列
queue<int>* cur = &q1;
if (!q2.empty()) cur = &q2;
cur->push(n);
}
void pop()
{
if (empty()) return;
queue<int>* cur = &q1;
queue<int>* another = &q2;
if (!q2.empty())
{
cur = &q2;
another = &q1;
}
// 将前面的那些转移到另一个队列中
while (cur->size() > 1)
{
int n = cur->front();
another->push(n);
cur->pop();
}
if(!cur->empty())
cur->pop();
}
int top()
{
if (empty()) return 0;
queue<int>& cur = q1;
queue<int>& another = q2;
if (!q2.empty())
{
another = q1;
cur = q2;
}
// 将前面的那些转移到另一个队列中
int n ;
while (!cur.empty())
{
n = cur.front();
another.push(n);
cur.pop();
}
return n;
}
bool empty()
{
return q1.empty() && q2.empty();
}
};
int main()
{
MyStack s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
cout << s.top();
s.pop();
s.push(5);
while (!s.empty())
{
cout << s.top();
s.pop();
}
}
10. 斐波那契数列
using namespace std;
int main()
{
for (;;)
{
int n;
cin >> n;
if (n == 1)
{
cout << 1 << endl;
continue;
}
int fibMinusOne = 1;
int fibMinusTow = 0;
int fibN = 0;
for (int i = 2; i <= n; i++)
{
fibN = fibMinusTow + fibMinusOne;
fibMinusTow = fibMinusOne;
fibMinusOne = fibN;
}
cout << fibN << endl;
}
return 0;
}
11. 旋转数组的最小数字
using namespace std;
// l指针一定在左子序列
// r指针一定在右子序列
int find(int *n, int l, int r)
{
if (n[l] < n[r]) return n[l]; // 处理 自身有序这种情况,可以直接返回
if (r - l == 1) return n[r]; // 左右两指针相遇,只差一个,则最小值是右子序列的第一个数字
int mid = (l + r) / 2.f;
if (n[mid] >= n[l]) return find(n, mid, r);
if (n[mid] <= n[r]) return find(n, l, mid);
}
int main()
{
int n[] = {3, 4, 5, 1, 2};
cout << find(n, 0, sizeof(n)/ sizeof(int) - 1);
}
12. 矩阵中的路径
using namespace std;
#define SIZE 4
// i, j 是当前点的位置
bool traceback(bool mask[][SIZE], char n[][SIZE], int r, int c, int i, int j, const char* s)
{
if (i < 0 || i >= r || j < 0 || j >= c)
return false;
if (mask[i][j]) return false; // 不能重复访问一个节点
if (n[i][j] == s[0])
{// 找下一部分
mask[i][j] = true; // 标记这个节点已经被访问过
if (strlen(s) == 1) return true; // 已经相等
if (traceback(mask, n, r, c, i, j - 1, s + 1) // 向左走
|| traceback(mask, n, r, c, i, j + 1, s + 1) // 向右走
|| traceback(mask, n, r, c, i - 1, j, s + 1) // 向上走
|| traceback(mask, n, r, c, i + 1, j, s + 1) // 向下走
)
return true;
else
{// 没有此路径
mask[i][j] = false;
return false;
}
}
else
return false;
}
bool find(bool mask[][SIZE], char n[][SIZE], int r, int c, const char* s)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
if (n[i][j] == s[0])
if (traceback(mask, n, r, c, i, j, s))
return true;
}
}
return false;
}
int main()
{
char n[][SIZE] = {
{'a', 'b', 't', 'g'},
{'c', 'f', 'c', 's'},
{'j', 'd', 'e', 'h' },
};
bool mask[][SIZE] = {
{0},
{ 0 },
{ 0 },
};
const char* s = "bfce";
//const char* s2 = "abfb";
if (find(mask, n, 3, 4, s))
cout << "the string is found" << endl;
else
cout << "can't find string" << endl;
}
13. 机器人的运动范围
using namespace std;
int sum(int i, int j)
{
int res = 0;
while (i > 0)
{
res += i % 10;
i /= 10;
}
while (j > 0)
{
res += j % 10;
j /= 10;
}
return res;
}
void find(int m, int n,int i, int j, int k, int& count, bool* visited)
{
if (i < 0 || i >= m || j < 0 || j >= n) return;
if (visited[n * i + j]) return;
if (sum(i, j) > k) return ;
if (!visited[n * i + j])
{
visited[n * i + j] = true;
count += 1;
}
find(m, n, i - 1, j, k, count, visited); // 向上走
find(m, n, i + 1, j, k, count, visited); // 向下走
find(m, n, i, j - 1, k, count, visited); // 向左走
find(m, n, i, j + 1, k, count, visited); // 向右走
}
int main()
{
for (;;)
{
int m, n, k;
m = 5;
n = 5;
k = 3;
int count = 0;
bool* visited = new bool[m *n];
for (int i = 0; i < m*n; i++)
visited[i] = 0;
find(m, n, 0, 0, k, count, visited);
cout << count;
}
}
14. 剪绳子
using namespace std;
int main()
{
int n = 8;
int *product = new int[n + 1];
// 边界
product[0] = 0;
product[1] = 1;
product[2] = 2;
product[3] = 3;
int max = 0;
for (int i = 4; i <= n; i++)
{
max = 0;
for (int j = 0; j <= i/2; j++)
{
int p = product[j] * product[i - j];
if (p > max) max = p;
product[i] = max;
}
}
max = product[n];
delete[] product;
cout << max;
}
15. 位运算
#include <string>
#include <math.h>
using namespace std;
int f(const string s)
{
int n = s.size();
int res = 0;
for (int i = 0; i < s.size(); i++)
{
char c = s[i];
int num = c - 'A' + 1;
res += num * pow((double) 26, (double)(--n));
}
return res;
}
int main()
{
for (;;)
{
string s;
cin >> s;
cout << f(s);
}
}
using namespace std;
int countOne(int n)
{
int res = 0;
while (n > 0)
{
res += (n & 1);
n = n >> 1;
}
return res;
}
int main()
{
for (;;)
{
int n;
cin >> n;
cout << countOne(n) << endl;
}
}
using namespace std;
int countOne(int n)
{
int count = 0;
unsigned int flag = 1;
while (flag)
{
if ((n & flag) != 0) count++;
flag = flag << 1;
}
return count;
}
int main()
{
for (;;)
{
int n;
cin >> n;
cout << countOne(n) << endl;
}
}
using namespace std;
int countOne(int n)
{
int count = 0;
while (n)
{
++count;
n = (n - 1) & n;
}
return count;
}
int main()
{
for (;;)
{
int n;
cin >> n;
cout << countOne(n) << endl;
}
}
16. 数值的整数次方
bool g_InvalidInput = false; // 保存错误信息
double PowerWithUnsignedExponent(double base, unsigned int exponent) // 计算正数幂
{
double result = 1.0;
for (int i = 1; i <= exponent; ++i)
result *= base;
return result;
}
double Power(double base, int exponent)
{
g_InvalidInput = false;
if (base == 0.0 && exponent < 0)
{// 不能对0求负幂,因为不能对0求倒数
g_InvalidInput = true;
return 0.0;
}
unsigned int absExponent = (unsigned int)exponent;
if (exponent < 0)
absExponent = (unsigned int)(-exponent);
double result = PowerWithUnsignedExponent(base, exponent);
if (exponent < 0)
// 如果是负数幂,就取倒数
result = 1.0 / result;
return result;
}
int main()
{
}
17. 打印从1到最大的n位数
using namespace std;
// 给n +1
bool Increment(char* n)
{
bool isOverflow = false;
int nTakeOver = 0; // 进位
int nLength = strlen(n);
for (int i = nLength - 1; i >= 0; i--)
{
int nSum = n[i] - '0' + nTakeOver; // 加上进位
if (i == nLength - 1)
nSum++;
if (nSum >= 10) // 需要进位
{
if (i == 0) // 如果已经到最高位了,即最后一个数字
isOverflow = true;
else
{
nSum -= 10;
nTakeOver = 1;
n[i] = '0' + nSum; //刷新
}
}
else
{
n[i] = '0' + nSum;
break;
}
}
return isOverflow;
}
// 输出数字,注意别输出补位的零
void PrintNumber(const char* n)
{
int len = strlen(n);
bool isBeginningZero = true;
for (int i = 0; i < len; i++)
{
if (n[i] == '0' && isBeginningZero)
{
continue;
}
isBeginningZero = false;
cout << n[i];
}
cout << '\n';
}
//
void PrintToMaxOfNDigits(int n)
{
if (n <= 0)
return;
char* number = new char[n + 1];
memset(number, '0', n);
number[n] = '\0';// 此时 字符串应该是 0000...000\0, n个0, 结尾一个\0
while (! Increment(number)) // 输出,直到位数已满
{
PrintNumber(number);
}
delete[] number;
}
int main()
{
for (;;)
{
int n;
cin >> n;
PrintToMaxOfNDigits(n);
}
}
using namespace std;
void PrintToMaxOfNDigits(int n)
{
if (n <= 0) return;
char* number = new char[n + 1];
number[n] = '\0';
for (int i = 0; i <= 9; i++)
{
number[0] = i + '0';
PrintToMaxOfNDigitsRecursively(number, n, 0);
}
}
// 输出数字,注意别输出补位的零
void PrintNumber(const char* n)
{
int len = strlen(n);
bool isBeginningZero = true;
for (int i = 0; i < len; i++)
{
if (n[i] == '0' && isBeginningZero)
{
continue;
}
isBeginningZero = false;
cout << n[i];
}
cout << '\n';
}
void PrintToMaxOfNDigitsRecursively(char* number, int length, int index)
{
if (index == length - 1)
{
PrintNumber(number);
}
for (int i = 0; i < 10; i++)
{
number[index + 1] = i + '0';
PrintToMaxOfNDigitsRecursively(number, length, index + 1);
}
}
int main()
{
for (;;)
{
int n;
cin >> n;
PrintToMaxOfNDigits(n);
}
}
18. 删除链表的结点
{
if(*head == NULL || node == NULL) return ;
// 如果要删除的不是尾结点
if(node->next != NULL)
{
ListNode* next= node->next;
node->m_pDate = next->m_pData;
node->next = next->next;
delete next;
next = NULL;
}
// 如果只有一个结点,删除这个节点
else if(*head == node)
{
delete node;
node = NULL;
*head= NULL:
}
else
{// 链表有多个节点,且删除尾结点,则尾结点的上一个节点的next应该是0,
// 这时候只能老老实实遍历了
ListNode* n = *head;
while(n->next != node)
{
n = n->next;
}
n->next = NULL;
delete node;
node = NULL;
}
}
19. 正则表达式匹配
using namespace std;
bool matchCore(char* str, char* pattern)
{
// 递归终点
if (*str == '\0' && *pattern == '\0') // 当两者都走完时,匹配
return true;
if (*str != '\0' && * pattern == '\0') // 当模式已经走完时,字符串还没走完,不匹配。(题目要求字符串中的所有字符匹配整个模式);翻过来的情况在下面的代码中判断,即*str = '\0’返回false。
return false;
if (*(pattern + 1) == '*')
{// 如果模式的第二个字符是* 或者模式允许任意字符出现任意多次即".*"模式
if (*str == *pattern || *pattern == '.' && *str != '\0')
{// 如果str的第一个字符和pattern的第一个匹配,则str跳过一个,或者pattern跳过这个*匹配(即匹配0次),或者两者都跳过
return matchCore(str + 1, pattern)
|| matchCore(str, pattern + 2)
|| matchCore(str + 1, pattern + 2);
}
else
{ // 如果不匹配,则跳过这个* 匹配(因为*允许出现0次前面的字符)
return matchCore(str, pattern + 2);
}
}
if (*str == *pattern || (*pattern == '.' && *str != '\0'))
return matchCore(str + 1, pattern + 1);
}
bool match(char* str, char* pattern)
{
if (str == NULL || pattern == NULL) return false;
return matchCore(str, pattern);
}
20. 表示数值的字符串
21. 调整数组顺序使得奇数位于偶数前面
using namespace std;
int main()
{
int n[] = {0, 1,2,3,4,5,6,7,8};
int size = sizeof(n) / sizeof(int);
int l = 0;
int r = size - 1;
while (l < r)
{
while ((n[r] & 1) == 0) --r; // 位运算符优先级低于比较运算符。位运算优先级一般很低,移位最低
while ((n[l] & 1) == 1) ++l;
if (l >= r) break;
n[r] ^= n[l];
n[l] ^= n[r];
n[r] ^= n[l];
l++;
r--;
}
for (int i = 0; i < size; i++)
cout << n[i] << " " << endl;
}
22. 获得链表中倒数第k个节点
{
if (k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
return NULL;
ListNode * pAhead = pHead;
ListNode * pBehind = pHead;
while (k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点, 走了k-1步
{
k--;
pAhead = pAhead->m_pNext;
}
if (k > 1 || pAhead == NULL) // 结点个数小于k,返回NULL
return NULL;
while (pAhead->m_pNext != NULL) // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
{
pBehind = pBehind->m_pNext;
pAhead = pAhead->m_pNext;
}
return pBehind; // 后面的指针所指结点就是倒数第k个结点
}
23. 链表中环的入口结点
24. 反转链表
25. 合并两个已排序的链表
26. 树的子结构
27. 翻转二叉树
* 28. 对称的二叉树
{
return isSymmetraical(pRoot, pRoot);
}
bool isSymmetraical(BinaryNode* pRoot1, BinaryNode* pRoot2) // root1往左走,root2往右走
{
if(pRoot1 == NULL && pRoot2 == NULL)
return true;
if(pRoot1 == NULL || pRoot2 == NULL) // 有一个不为空
return false;
if(pRoot1->m_Value != pRoot2->m_Value)
return false;
return isSymmetraical(pRoot1->m_left, pRoot2->m_right)
&& isSymmetraical(pRoot1->m_right, pRoot2->m_left);
}
29. 顺时针打印矩阵
30. 包含min函数的栈
31. 栈的压入、弹出序列
32. 从上到下打印二叉树(二叉树层次遍历/广度优先遍历)
33. 二叉搜索树的后序遍历序列
34. 二叉树中和为某一值的路径
35. 复杂链表的复制
36. 二叉搜索树转化为双向链表
37. 序列化二叉树
* 38. 字符串的排列
void Permutation(char* pStr, char* pBegin);\
void Permutation(char* pStr)
{
if(pStr == nullptr)
return;
Permutation(pStr, pStr);
}
void Permutation(char* pStr, char* pBegin)
{
if(*pBegin == '\0')
{
printf("%s\n", pStr);
}
else
{
for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
{
char temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
Permutation(pStr, pBegin + 1); // 处理后面的
temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
}
}
}
// ====================测试代码====================
void Test(char* pStr)
{
if(pStr == nullptr)
printf("Test for nullptr begins:\n");
else
printf("Test for %s begins:\n", pStr);
Permutation(pStr);
printf("\n");
}
int main(int argc, char* argv[])
{
Test(nullptr);
char string1[] = "";
Test(string1);
char string2[] = "a";
Test(string2);
char string3[] = "ab";
Test(string3);
char string4[] = "abc";
Test(string4);
return 0;
}
* 39. 数组中出现次数超过一半的数字
* 40. 最小的k个数(TOP K问题)
其实很简单,大家都知道Quicksort每次分割后即把比轴小的值和比轴大的值给cut开了。那么,假如是想求前k小(以下源码即是),我就只关心比轴小的一边,继续分割这一边,直到如果分割后的元素没有k个了,就结束循环。我们用源码说话:
快速排序的思想是使用一个基准元素将数组划分成两部分,左侧都比基准数小,右侧都比基准数大。
给定数组array[low…high],一趟快排划分后的结果有三种:
1)如果基准数左侧元素个数Q刚好是K-1,那么在基准数左侧(包含基准数本身),即为TopK的所有元素。
2)如果基准数左侧元素个数Q小于K-1,那么说明基准数左侧的Q个数都是TopK里的元素,只需要在基准数的右侧找出剩下的K-Q个元素即可。问题转化成了以基准数下标为起点,高位(high)为终点的Top(K-Q)。递归下去即可。
3)如果基准数左侧元素个数Q大于K-1,说明第K个位置,在基准数的左侧,需要缩小搜索范围,在低位(low)至基准数位置重复递归即可,最终问题会转化成上面两种情况。