C++ : 剑指offer(4-20)
文章目录
4、二维数组的查找
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int rows = array.size();
int columns = array[0].size();
int row = 0;
int column = columns - 1;
bool found = false;
if(rows>0&&columns>0){
while(row<rows&&column>=0){
if(target<array[row][column]){
--column;
}
else if(target==array[row][column]){
found = true;
break;
}
else{
++row;
}
}
}
return found;
}
};
思路:从数组的右上角或者左下角进行逐行或逐列删除,直至搜索完毕。
- 另外需要掌握利用vector创建二维数组的各项操作;
5、替换空格
题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
class Solution {
public: // 该题需要注意数组是否原本就有足够长度
void replaceSpace(char *str,int length) {
// length为字符数组的总长度(包含\0)
if(str&&length>0){ // 字符数组不为空,需要处理
int oldlength = 0;
int num = 0;
int i = 0;
while(str[i]!='\0'){
if(str[i]==' ') ++num; // 统计空格数目
++oldlength; // 数组本身长度
++i;
}
if(num>0){ // 存在空格需要替换
int newlength = oldlength+num*2;
if(newlength<=length){ // 留有足够空间
int indexOfNew = newlength;
int indexOfOld = oldlength;
while(indexOfOld>=0&&indexOfNew>indexOfOld){
if(str[indexOfOld]==' '){
str[indexOfNew--]='0';
str[indexOfNew--]='2';
str[indexOfNew--]='%';
}
else{
str[indexOfNew--]=str[indexOfOld];
}
--indexOfOld;
}
}
}
}
}
};
思路:首先明确str是一个类似char str[20]=“hello world”;的静态字符数组,传参到函数为char* str类型,但可以使用下标操作;其次,明确str要发生长度变化,故必须实现留有足够的长度空间;然后,为了节省计算量,从后往前逐步移动字符数组,将空格进行代替;
6、倒序打印链表
题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> vec;
stack<int> stk;
if(head==nullptr||!(head->val)){return vec;}
ListNode * pNode = head;
stk.push(pNode->val);
while(pNode->next!=nullptr){
stk.push(pNode->next->val);
pNode = pNode->next;
}
while(!stk.empty()){
vec.push_back(stk.top());
stk.pop();
}
return vec;
}
};
思路:利用栈stack解决问题;
7、重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int size1 = pre.size(), size2 = vin.size();
if(size1==0&&size2==0) return nullptr;
if(size1!=size2||size1<0||size2<0) return nullptr;
int RootValue = pre[0];
TreeNode* root = new TreeNode(RootValue);
if(pre.size()==vin.size()&&pre.size()==1){
if(pre[0]==vin[0]){
return root;
}
else{
return nullptr;
}
}
int index = 0;
while(vin.size()>1&&vin[index]!=RootValue){
++index;
}
if(vin[index]!=RootValue){
return nullptr;
}
if(index>0){
vector<int> pre_left(pre.begin()+1,pre.begin()+index+1);
vector<int> vin_left(vin.begin(),vin.begin()+index);
root->left = reConstructBinaryTree(pre_left,vin_left);
}
if((size2-index-1)>0){
vector<int> pre_right(pre.begin()+index+1,pre.end());
vector<int> vin_right(vin.begin()+index+1,vin.end());
root->right = reConstructBinaryTree(pre_right,vin_right);
}
return root;
}
};
思路:较为复杂,首先需要十分熟悉二叉树的三种遍历方式:前序遍历、中序遍历和后续遍历以及其各自的性质特点,从根节点入手,进而可以区分出左右子树的节点序列(前序和中序),演变到左右两个子树的重构问题,形成递归形式;
8、二叉树的下一个节点
题目描述
给定一个二叉树和其中一个节点,找出其在中序遍历序列中的下一个节点并返回其指针;其中二叉树中的节点有三个指针,分别指向左右子节点和自己的父节点;
BinaryTreeNode* findnext_inorder(BinaryTreeNode* root,BinaryTreeNode* node){
if(root==nullptr) return nullptr;
BinaryTreeNode* Temp;
if(node->rightchild!=nullptr){ // 有右子节点
Temp = node->rightchild;
while(Temp->leftchild!=nullptr){
Temp = Temp->leftchild;
}return Temp;
}
else{ // 无右子节点
if(node==root) return nullptr;
if(node->father->leftchild==node){ // 父节点的左子节点
return node->father;
}
else{ // 是父节点的右子节点
Temp = node->father;
while(true){
if(Temp->father==nullptr) return nullptr; // 序列最后一个节点
if(Temp->father->leftchild==Temp) return Temp->father; // 父节点的右节点
Temp = Temp->father;
}
}
}
}
思路:问题比较复杂,需要先分析中序遍历序列的特性,建议首先在纸上列出所有情况并分别分析:
1、当二叉树为空,返回nullptr;
2、当该节点有右子节点时: 问题转化为求该右子节点树的最左子节点;
3、当该节点没有右子节点时:
(1)、该节点本身为根节点时,直接返回nullptr;
(2)、该节点是父节点的左子节点,返回其父节点;
(3)、该节点是父节点的右子节点,返回一个是其本身父节点的左子节点的父节点;
9、用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
class Solution
{
public:
void push(int node) {
if(!stack2.empty()){
while(!stack2.empty()){
stack1.push(stack2.top());
stack2.pop();
}
}
stack1.push(node);
}
int pop(){
int temp;
if(!stack1.empty()){
while(!stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
}
temp = stack2.top();
stack2.pop();
return temp;
}
private:
stack<int> stack1;
stack<int> stack2;
};
思路:两个栈互相倒可以调换顺序,用来push或者pop;其中stack1用来push,stack2用来pop;
10、斐波那契数列(跳台阶)
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
class Solution {
public:
int jumpFloor(int number) {
if(number==0) return 0;
if(number==1) return 1;
if(number==2) return 2;
int f = 0;
int n1 = 1, n2 = 2;
for(int i=2;i<number;++i){
f = n1 + n2;
if(n1<=n2){
n1 = f;
}
else{
n2 = f;
}
}
return f;
}
};
思路:跳台阶问题本质上是一个斐波那契数列,一定要转化为这种问题才好解决;
该问题实质上是一个动态规划问题,将大问题分解为多个小问题的组合;
(尽量不要用递归,速度慢)
思路延伸: 变态跳台阶(可以一次跳1到n阶台阶,共多少种跳法)
f(n)=f(n-1)+f(n-2)+…+f(1)+f(0);
f(n-1)=f(n-2)+f(n-3)+…+f(1)+f(0);
相减得到: f(n)=2f(n-1);
可以利用递归、循环求解和规律公式:return pow(2,n-1);
11、旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
class Solution {
public:
static int minNumberInRotateArray(vector<int> rotateArray) {
int len = rotateArray.size();
if(len==0) return 0;
int l = 0;
int r = len - 1;
int result = l;
while(rotateArray[l]>=rotateArray[r]){ // 如果是个旋转序列
if((r-l)==1) return rotateArray[r]; // 循环截止
int mid = (l + r) / 2;
if(rotateArray[mid]==rotateArray[r]&&rotateArray[l]==rotateArray[r]){
return findmin(rotateArray,l,r);
}
if(rotateArray[mid]>=rotateArray[l]){
l = mid; // 最小值在右侧序列中
}
else if(rotateArray[mid]<=rotateArray[r]){
r = mid; // 最小值在mid或mid左侧
}
result = rotateArray[r];
}
return result;
}
private:
static int findmin(vector<int>& rotateArray,int l,int r){
int min = rotateArray[0];
for(int i=l;i<=r;++i){
if(min>rotateArray[i]) min = rotateArray[i];
}
return min;
}
};
思路:一个可以应用二分查找的题,需熟练掌握二分写法;
12、矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
if(matrix==nullptr||rows<=0||cols<=0||str==nullptr) return false;
int pathLength = 0;
int ifsearched[rows*cols];
memset(ifsearched,0,rows*cols*4);
for(int row=0;row<rows;++row){
for(int col=0;col<cols;++col){
if(matrix[row*cols+col]==str[0]){
if(hasNext(matrix,rows,cols,str,row,col,ifsearched,pathLength))
return true;
}
}
}
return false;
}
bool hasNext(char* matrix,int rows,int cols,char* str,int row,int col,int* ifsearched,int& pathLength){
if(str[pathLength]=='\0') return true;
bool hasnext = false;
if(row>=0 && col>=0 && row<rows && col<cols && matrix[row*cols+col]==str[pathLength] && ifsearched[row*cols+col]==0){
++pathLength;
ifsearched[row*cols+col] = 1;
hasnext = hasNext(matrix,rows,cols,str,row+1,col,ifsearched,pathLength)
||hasNext(matrix,rows,cols,str,row-1,col,ifsearched,pathLength)
||hasNext(matrix,rows,cols,str,row,col+1,ifsearched,pathLength)
||hasNext(matrix,rows,cols,str,row,col-1,ifsearched,pathLength);
if(!hasnext){
--pathLength;
ifsearched[row*cols+col] = 0;
}
}
return hasnext;
}
};
思路:回溯法,逐个查找,难点在于程序思路和设计,以及撰写,细节很多要注意。
13、机器人的运动范围
题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
class Solution {
public:
int movingCount(int threshold, int rows, int cols)
{
if(rows<1&&cols<1&&threshold<0){ return 0; }
int searched[rows*cols]; memset(searched,0,rows*cols*4);
int row = 0, col = 0;
return hasnext(threshold,rows,cols,row,col,searched);
}
int hasnext(const int& threshold,const int& rows,const int& cols,int row,int col,int* searched){
int count = 0;
if(row<rows&&row>=0&&col<cols&&col>=0&&searched[row*cols+col]!=1){
if(check(threshold,row,col)){
searched[row*cols+col] = 1;
count = 1 + hasnext(threshold,rows,cols,row+1,col,searched)
+ hasnext(threshold,rows,cols,row-1,col,searched)
+ hasnext(threshold,rows,cols,row,col+1,searched)
+ hasnext(threshold,rows,cols,row,col-1,searched);
}
}
return count;
}
bool check(const int& threshold,int row,int col){
int sum = 0;
bool b = false;
while(row>0){
sum = sum + row % 10;
row = row / 10;
}
while(col>0){
sum = sum + col % 10;
col = col / 10;
}
if(sum<=threshold) b = true;
return b;
}
};
思路:也是一道回溯法求路径问题,主要注意细节和步骤;
14、剪绳子
题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。(2 <= n <= 60)
class Solution {
public:
int cutRope(int number) {
if(number==2) return 1;
if(number==3) return 2;
int list[number];
list[1] = 1;
list[2] = 2;
list[3] = 3;
list[4] = 4;
for(int i=5;i<=number;++i){
int max = 0;
for(int j=1;j<i;++j){
if(list[j]*list[i-j]>max) max = list[j]*list[i-j];
}
list[i] = max;
}
return list[number];
}
};
思路:典型的动态规划问题(求最优解,可分解为子问题,子问题之间有计算重合部分),自上而下分析问题,自下而上解决问题(求绳子长度等于每一个数的最优解)。
15、二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
class Solution {
public:
int NumberOf1(int n) {
int num = 0;
for(int i=0;i<sizeof(n)*8;++i){
if(n&1){
++num;
}
n = n >> 1;
}
return num;
}
};
思路:灵活运用位运算如n&1。
16、数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
bool Power_InvalidInput = false; // 全局变量
bool double_equal(double& base,double number){
const double epsilon = 1e-6;
if(base-number<number*epsilon){
return true;
}
else{
return false;
}
}
class Solution {
public:
double Power(double base, int exponent) {
// 当底数为0而指数为负时,输入不合法
// double类型的相等判断不能通过==号
if(double_equal(base,0.0)&&exponent<0){
Power_InvalidInput = true;
return 0.0;
}
double result = 1.0;
// 必须考虑到指数有正有负的情况
int abs_exponent;
if(exponent<0){
abs_exponent = - exponent;
}else{
abs_exponent = exponent;
}
for(int i=0;i<abs_exponent;++i){
result = result * base;
}
if(exponent<0){
result = 1.0 / result;
}
return result;
}
};
思路:考察思维全面性,需要考虑到底数为0,指数为负的情况,同时注意非法输入的处理方式、double类型的比较方法等;
附:(求指数幂的O(logn)复杂度递归方法,e>0)
double getPow(double base,int e){
if(e==0) return 1.0;
if(e==1) return base;
double result = 1.0;
result = getPow(base,e>>1);
result = result * result;
if(e&1==1) result = result * base;
return result;
}
17、打印从1到最大的n位数
题目描述
输入数字n,按顺序打印从1到最大的n位十进制整数。
void Print1ToMaxOfDigitsRecursively(char* number,int length,int index){
if(index==length-1){ // 递归终止条件
print_str(number);
return;
}
for(int i=0;i<10;++i){ // 每一位不断循环增加
number[index+1] = i + '0';
Print1ToMaxOfDigitsRecursively(number,length,index+1); // 再下一位
}
}
void Print1ToMaxOfDigits(int n){
if(n<=0) return; // 非法输入处理
char number[n+1];
number[n] = '\0';
for(int i=0;i<10;++i){ // 最高位的循环(即321中的3),循环频率最慢
number[0] = i + '0'; // 最高位的不断增加
Print1ToMaxOfDigitsRecursively(number,n,0); // 进入次高位不断增加的循环(同时负责递归结束时打印工作)
}
}
思路:首先必须考虑全面,该数字并没有限制在int的范围内,可能会非常大,故需要利用字符串来代替数字进行打印,涉及到全排列或者顺序排列问题,用递归是不错的方法;
18、删除链表中的节点(1)(2)
题目描述
(1)、在O(1)时间内删除指定的链表节点;
// 链表结构
struct ListNode{
int val;
ListNode* p_Next;
ListNode(int value,ListNode* next=nullptr):val(value),p_Next(next){}
};
// 删除链表节点操作
void DeleteNode(ListNode** pHead,ListNode* pDelete) {
if (pHead == nullptr || *pHead == nullptr || pDelete == nullptr) return; // 为空
if (pDelete == *pHead && pDelete->p_Next == nullptr) { // 为头且无下节点
delete *pHead;
*pHead = nullptr;
pDelete = nullptr;
return;
}
if(pDelete != *pHead && pDelete->p_Next == nullptr) { // 不为头且为尾,循环法
ListNode* pNode = *pHead;
while(pNode->p_Next!=pDelete){
pNode = pNode->p_Next;
}
pNode->p_Next = nullptr;
delete pDelete;
pDelete = nullptr;
return;
}
// 其他情况(复制下一节点到当前节点、删除下一节点)
ListNode* pDeleteNext = pDelete->p_Next;
pDelete->val = pDeleteNext->val;
pDelete->p_Next = pDeleteNext->p_Next;
delete pDeleteNext;
pDeleteNext = nullptr;
}
思路:思路特殊,需要转变思路,将要删除节点的下一节点复制给当前节点,删除下一节点即可;但需要注意各种特殊情况,比如删除头节点、只有头节点、链表长度不为1且删除尾节点时等等,都需考虑;另外注意尽量将链表头节点参数设定为指针的指针,防止在链表为空时添加、链表长度为1时删除后,头节点指针发生改变的情况;
题目描述
(2)、在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
// 链表结构
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{ // 因为传入了指针pHead,参数也是指针,故函数内的pHead是一个独立的个体,
// 单独改变函数内的pHead,将其置为链表头,并返回pHead;
if(pHead==nullptr||pHead->next==nullptr) return pHead;
ListNode* left = pHead;
ListNode* right = pHead->next;
ListNode* toBeDel = nullptr;
if(left->val==right->val){ // 若头节点也是重复节点
delete left; left = nullptr;
while(right->next!=nullptr&&right->next->val==right->val){
toBeDel = right;
right = right->next;
delete toBeDel; toBeDel = nullptr;
}
pHead = right->next;
delete right; right = nullptr;
return deleteDuplication(pHead);
}
// 总循环
while(right!=nullptr&&left->val!=right->val){
if(right->next==nullptr) break; // 循环结束
else if(right->next->val==right->val){ // 从right开始是重复节点
while(right->next!=nullptr&&right->next->val==right->val){
toBeDel = right;
right = right->next;
delete toBeDel; toBeDel = nullptr;
}
toBeDel = right;
right = right->next;
delete toBeDel; toBeDel = nullptr;
left->next = right; // 连接链表
}
else{ // 当前无重复节点,往后推进
left = right;
right = right->next;
}
}
// 所有处理结束,返回头节点
return pHead;
}
};
思路:链表节点删除看似简单,实则非常复杂,需要考虑各种情况,还有传参指针的问题,注意在删除链表程序中最好都建立一个节点指针toBeDel专门删除节点;
19、正则表达式匹配
题目描述
请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
class Solution {
public:
bool match(char* str, char* pattern)
{
if(str==nullptr||pattern==nullptr)
return false; // 当str或patten为空时;
return matchCore(str,pattern); // 否则直接转入子函数判断
}
bool matchCore(char* str,char* pattern){
if(*str=='\0'&&*pattern=='\0')
return true; // 当两者都走到头时,匹配结束,返回true
if(*str!='\0'&&*pattern=='\0')
return false; // 当字符串未走完,但模板已走完时,递归路线的这种匹配方式出错
// 当字符串走完,但模板没走完时,有可能后面有c*模式,放在下一个if中讨论
if(*(pattern+1)=='*'){ // 当模板出现c*形式时
if(*str==*pattern||(*pattern=='.'&&*str!='\0')){
// 当(字符相等)或者(模板是.*形式且数组没有结束)时
// 在该情况下,以下路线只要有一条行得通,就会返回true
return matchCore(str+1,pattern) // 匹配数组一个当前字符的匹配路线(还有其他匹配字符在后面)
|| matchCore(str+1,pattern+2) // 匹配上最后一个字符,c*模式结束
|| matchCore(str,pattern+2); // 没有匹配,直接跳过c*模式
}
else{
return matchCore(str,pattern+2); // 有c*模式但字符不相等,直接跳过
}
}
if(*str==*pattern||(*pattern=='.'&&*str!='\0')){
// 如当前(两者头字符相等)或(遇到.模式)时
return matchCore(str+1,pattern+1);
}
return false; // 最后不能忘了return false;
}
};
思路:字符串匹配问题,该题目涉及到可以是若干个字符匹配一个字符的问题,就要想办法把问题转化为递归求解路径问题;匹配了当前部分,剩下部分的匹配交给递归。把每一种匹配方式通过递归和或运算的方式结合,思想类似于求解矩阵路径;另外灵活使用字符串指针的解引用方式,该题目不需要用到下标运算;
20、表示数值的字符串
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
class Solution {
public:
bool isNumeric(char* string)
{
if(string==nullptr) return false;
bool isnum = scanInterger(&string); // 先判断+-整数部分
if(*string=='.'){ // 如果整数部分有小数点
++string;
// 小数点前有整数或小数点后有整数都可,所以用或,这里注意||的前后顺序
isnum = scanUnsignedInterger(&string) || isnum;
}
if(*string=='e'||*string=='E'){ // 如果有e或E指数部分
++string;
// 指数部分的判断,要求eE后面必须有个带符号的整数部分
isnum = isnum && scanInterger(&string);
}
return isnum && *string=='\0'; // 判断完后string需要到达末尾对齐,才算完成匹配
}
bool scanUnsignedInterger(char** string){ // 对无符号整数进行遍历
// 这里传入的参数是字符串指针的指针,为了使string在函数外发生改变
const char* before = *string;
while(**string!='\0'&&**string>='0'&&**string<='9'){
++(*string);
}
return *string>before; // 如果存在无符号整数,则为true
}
bool scanInterger(char** string){ // 对有符号整数的+-号进行判断
if(**string=='+'||**string=='-'){
++(*string);
}
return scanUnsignedInterger(string); // 调用无符号整数遍历
}
};
思路:一道非常复杂的题,如果用常规循环按条件求解的方式会非常复杂,上述的思路较为简便,即移动string指针,让前面的匹配与后面的匹配紧密结合,分解出了符号±和eE以及小数点,按组合顺序依次判断,化繁为简;两个问题:string指针传参中要求发生改变的问题,以及与或运算符的结合顺序问题;