剑指offer学习笔记:3.3 代码的完整性

从功能测试,边界测试和负面测试三个方面设计测试用例,保证代码的完整性。

关于异常处理的三种方式

优点 缺点
返回值 和系统api一致 不能方便使用计算结果
全局变量 能够方便使用计算结果 用户可能会忘记检查全局变量
异常 可以为不同的出错原因定义不同异常类,逻辑清晰明了 有些语言不支持抛出异常,抛出异常时对性能有负面影响


面试题11:数值的整数次方

题目:实现函数double Power(double base, int exponent), 求base的exponent次方。不得使用库函数,同时,不考虑大数问题。
牛客网链接 https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&&tqId=11165&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

class Solution {
public:
   bool Invalid_flag = false;   // 全局变量,标志输入是否合法
   double Power(double base, unsigned int exponent)
   {
       double result = 1;
       for(int i = 1; i <= exponent; i++)
       {
           result *= base;
       }
       return result;
   }
   bool equal(double a, double b)
   {
       if ((a-b) < 0.0000001 && (a-b) > 0.0000001)
       {
           return true;
       }
       return false;
   }
   double Power(double base, int exponent) {
       Invalid_flag = false;
       if(equal(base, 0.0) && exponent < 0)  // 注意小数相等判断不能直接用==
       {
           Invalid_flag = true;   // 输入数据无效
           return 0;
       }
       unsigned int tmp = (unsigned int)exponent;
       if (exponent < 0)
       {
           tmp = (unsigned int)(-exponent);
       }
       double result = Power(base, tmp);
       if (exponent < 0)
       {
           result = 1 / result;
       }
       return result;       
   }
};

考察点:
1.考虑到指数为负数的情况。尤其是指数为负数,底数为0的情况。注意在c++中判断小数相等不能直接用==,因为计算机在表示小数的时候是有误差的,要判断两个数绝对值的差在一个很小的范围内。
2.考察对于异常处理的方式。
3.乘方可以进行拆分
4.用右移代替除法,与代替余数运算,可以调高运算效率

面试题12:打印1到最大的n位数

题目:输入数字n,按顺序打印出从1到最大的n位10进制数。比如输入3,则打印1,2,3一直到999。 两种方法 1.字符串模拟加法 2.全排列,递归实现
leetcode链接
https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/ leetcode上这个链接其实有问题,他的返回定义成了vector<int>既然是int,其实没有考虑大数问题

//
// Created by Xue,Lin on 2020/6/25.
//
#ifndef UNTITLED_PRINT_N_NUMBER_H
#define UNTITLED_PRINT_N_NUMBER_H
#include "string"
#include "iostream"
using namespace std;
// 方法1:字符串模拟加法操作
// 因为数字可能存在越界情况,因此用长度为n+1的字符串模拟数字,+1的原因是字符串末尾需要'/0'结束符
// increase函数进行+1操作,当达到上限返回true。printNumber函数进行打印操作
void printNumber(char* number)
{
   // 打印非0开始数字
   // 因为有字符串结束标志符,可以在函数中进行长度判断
   bool start = true;
   for(int i = 0; i < strlen(number); i++)
   {
       if (number[i] == '0' && start)
       {
           continue;
       }
       start = false;
       cout << number[i];
   }
   cout << " ";
}
// 注意需要变量保留进位标志
// 注意结束标志是当前已是n位数,且产生了进位
bool increase(char* number)
{
   // 达到n位且产生进位时置true,外部循环可以结束
   bool overFlow = false;
   // 进位标志符
   int takeOver = 0;
   int len = strlen(number);
   for(int i = len - 1; i >= 0; i--)
   {
       // cout << "number: " << number << endl;
       // cout << "i: " << i << " takeover: " << takeOver << endl;
       int tmp;
       if (i == len - 1)
       {
           // 最后一位+1
           tmp = number[i] - '0' + 1;
       } else{
           // 其余位+进位
           tmp = number[i] - '0' + takeOver;
       }
       number[i] = tmp % 10 + '0';
       takeOver = tmp / 10;
       if (i == 0 && takeOver != 0)
       {
           overFlow = true;
       }
       // cout << "takeOver: " << takeOver << endl;
   }
   return overFlow;
}
void printMaxN(int n)
{
   if(n <= 0)
   {
       return;
   }
   char* number = new char[n+1];
   memset(number, '0', n);  
   number[n] = '\0';
   while(!increase(number))
   {
       printNumber(number);
   }
   delete [] number;
}
// 方法2:全排列,递归
// 每一位都有0-9 10种选择
// 递归结束条件是已经设置到数字的最后一位
void printMaxRec(char* number, int index)
{
   int len = strlen(number);
   // cout << "len: " << len << endl;
   if (index == len - 1)
   {
       printNumber(number);
       return;
   }
   for(int i = 0; i <= 9; i++)
   {
       number[index + 1] = '0' + i;
       printMaxRec(number, index + 1);
   }
}
void printMaxNRec(int n)
{
   if (n <= 0)
   {
       return;
   }
   // cout << "n:" << n << endl;
   char* number = new char[n+1];
   memset(number, '0', n);  // 注意一定要有这个赋值操作,不然strlen长度是0 !!!
   number[n] = '\0';
   // cout << "len1: " << strlen(number) << endl;
   for(int i = 0; i <= 9; i++)
   {
       number[0] = '0' + i;
       printMaxRec(number, 0);
   }
}
#endif //UNTITLED_PRINT_N_NUMBER_H

考察点:大数问题
当存在大数越界风险,需要用字符串模拟数字
注意用字符串模拟数字相加时进位和判断长度是否超限方法。
注意打印函数需要去前面无效0

本题扩展:在前面代码中,我们都是用一个char型字符表示十进制数字中的一位,但是char占8个bit,可以表示256个字符,用来表示10进制有些浪费,有没有其余更高效的方法来表示大数。
答:想到的方法是用bit来表示,用4个bit来表示一个数。但是感觉写起来比较麻烦,不研究了。

相关题目:定义一个函数,在该函数中可以实现任意两个整数的加法。

//
// Created by Xue,Lin on 2020/6/25.
//

#ifndef UNTITLED_SUM_H
#define UNTITLED_SUM_H
#include <iostream>
#include "math.h"
using namespace std;
// 任意两个整数的和,是个大数问题,两个整数的和可能会超出int
// 相比+1这个大数问题,这个函数需要增加考虑负数情况
// 一个加法函数,一个打印函数
void printNumber(char* number)
{
   // 符号在函数外处理,打印的时候不考虑
   bool start = true;
   for(int i = 0; i < strlen(number); i++)
   {
       if (number[i] == '0' && start)
       {
           continue;
       }
       start = false;
       cout << number[i];
   }
   cout << " ";
}
void sum(const int a, const int b)
{
   // 如果是一个正数,一个负数,肯定不会超限
   if ((a >= 0 && b <= 0) || (a <= 0 && b >= 0))
   {
       cout << a + b << endl;
       return;
   }
   // 这里需要判断下a和b的符号,若是负数,可以先输出-号
   // 这里有个坑,负数最小 -2^31,正数最大2^31-1 转换会有问题
// 这里转换下的原因是我更习惯操作正数,没有其余原因
   int tmpA = a;
   int tmpB = b;
   if(a < 0)
   {
       cout << '-';
       tmpA = -1 - a;
       tmpB = -1 - b;
   }
   // 我有个不一样的想法
   // 不用转换字符串
   // c++带符号int最多2147483647,为10位数
   // 把进位和最高位记录下来,再加一下就可以了
   int max = int(pow(10, 9)); // 注意pow返回是double
   int a1 = tmpA / max;
   int b1 = tmpB / max;
   int a2 = tmpA % max;
   int b2 = tmpB % max;
   int low;
   if (a < 0)
   {
       low = a2 + b2 + 2;
   } else{
       low = a2 + b2;
   }
   /*
   cout << "a1: " << a1 << " b1: " << b1;
   cout << " a2: " << a2 << " b2: " << b2;
   cout << " low: " << low << endl;
    */
   int high = low / max + a1 + b1;
   if (high != 0)
   {
       cout << high << low << endl;
   } else{
       cout << low << endl;
   }

   // printNumber(number);
   return;
}
#endif //UNTITLED_SUM_H

考察点:由于没有限定两个数大小范围,需要当做大数来处理。同时,需要考虑,如果输入数字中有负数,应该如何处理。

面试题13:在o(1)时间内删除链表节点

给定单向链表的头指针和一个节点指针,定义一个函数,在o(1)时间内删除该节点链表节点和函数的定义如下:保证要删除节点一定在链表中
leetcode链接 https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/ 不太一样,还是自己写吧,正好再练下建立链表

struct ListNode   {  int m_pValue;  ListNode* m_pNext; };
void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
//
// Created by Xue,Lin on 2020/6/25.
//
#ifndef UNTITLED_DELETE_VECTOR_NODE_H
#define UNTITLED_DELETE_VECTOR_NODE_H
#include<iostream>
#include<vector>
using namespace std;
struct ListNode{
   int val;
   ListNode* next;
};
ListNode* createList(vector<int> arr)
{
   if (arr.size() == 0)
   {
       return NULL;
   }
   ListNode* head = new ListNode;
   head->val = arr[0];
   ListNode* pre = head;
   for(int i = 1; i < arr.size(); i++)
   {
       ListNode* tmp = new ListNode;
       tmp->val = arr[i];
       pre->next = tmp;
       pre = pre->next;
   }
   pre->next = NULL;
   return head;
}
void printList(ListNode* head)
{
   if (head == NULL)
   {
       return;
   }
   ListNode* tmp = head;
   while(tmp != NULL)
   {
       cout << tmp->val << " ";
       tmp = tmp->next;
   }
   cout << endl;
}
void deleteNode(ListNode** pListHead, ListNode* pToBeDelete)
{
   // 因为要求时间复杂度是1,不能直接遍历
   if (pListHead == NULL || *pListHead == NULL)
   {
       return;
   }
   // 要删除节点不是尾节点
   if(pToBeDelete->next != NULL)
   {
       ListNode* next = pToBeDelete->next;
       pToBeDelete->val = next->val;
       pToBeDelete->next = next->next;
       delete next;
       next = NULL;
   } else if(*pListHead == pToBeDelete)
   {
       // 链表只有一个节点
       delete pToBeDelete;
       pToBeDelete = NULL;
       *pListHead = NULL;
   } else
   {
       // 多个节点,但是是尾节点
       /* 本来以为这么直接写会成功的,但是并没有,为什么这样不行呢。。
       delete pToBeDelete;
       pToBeDelete = NULL;
        */
       ListNode* tmp = *pListHead;
       while(tmp->next != pToBeDelete)
       {
           tmp = tmp->next;
       }
       tmp->next = NULL;
       delete  pToBeDelete;
       pToBeDelete = NULL;
   }
}
#endif //UNTITLED_DELETE_VECTOR_NODE_H
// main中
   vector<int> arr = {2,3,1};
   ListNode* head = createList(arr);
   deleteNode(&head, head->next->next);
   printList(head);

解题思路:
要删除节点,首先想到是找到该节点前一个节点,然后将其next指向要删除节点next,但是找到前一个节点需要从头指针开始遍历,不符合时间复杂度o(1)要求。
变换思路,要删除当前节点,其实可以用当前节点next覆盖掉当前节点,然后删除当前节点的next节点,不需要从前往后遍历,时间复杂度o(1)。
注意要删除节点是尾节点和链表只有一个节点的情况。
leetcode 上那个题因为给的是value,只能找前一个节点,注意判断删除节点是不是头节点:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if (head->val == val)
        {
            head = head->next;
            return head;
        }
        ListNode* index = head;
        while(index->next != NULL)
        {
            if (index->next->val == val)
            {
                index->next = index->next->next;
                break;
            }
            index = index->next;
        }
        return head;
    }
};

面试题14:调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数,调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
leetcode链接 https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/

class Solution {
public:
   vector<int> exchange(vector<int>& nums) {
       // 双指针
       vector<int> result = nums;
       int begin = 0, end = result.size() - 1;
       while(begin <= end)
       {
           if (result[begin] % 2 != 0)
           {
               begin++;
               continue;
           }
           if (result[end] % 2 == 0)
           {
               end--;
               continue;
           }
           int tmp = result[end];
           result[end] = result[begin];
           result[begin] = tmp;
       }
       return result;
   }
};

解题思路:首尾双指针。尾指针指向遇到的第一个奇数,首指针指向遇到的第一个偶数,交换。
升级:把判断条件拎出来设计成独立函数,可以提高代码兼容性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342