题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
考察基本知识需要注意的几个问题:
CMyString & CMyString::operator==(const CMyString &str)
{
if(this == &str)
return *this;
delete []m_pData;
m_pData = nullptr;
m_pData = new char(strlen(str.m_pData)+1);
strcpy(m_pData ,str.m_pData);
return *this;
}
在分配内存之前先用delete释放了实例m_pData的内存,如果此时内存不足导致new char抛出异常,则m_pData将是一个空指针,这样非常容易引起程序崩溃。违背了异常安全性准则。
在赋值运算符函数中实现异常安全性有两种方法:
CMyString & CMyString::operator==(const CMyString &str)
{
if(this == &str)
return *this;
m_pData = new char(strlen(str.m_pData)+1);
delete []m_pData;
m_pData = nullptr;
strcpy(m_pData ,str.m_pData);
return *this;
}
CMyString & CMyString ::operator==(const CMyString &str)
{
if(this ! = &str)
{
CMyString temp = CMyString(str);
char* pTemp = temp.m_pData;
temp.m_pData = m_pData;
m_pData = pTemp ;
}
return *this;
}
这时由于temp是一个局部变量,当运行到if结束就会自动调用析构函数释放temp.m_pData内存,而temp.m_pData指向原来m_pData,也就是释放了原来实例的内存。
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:按照剑指offer提供的思路,首先,可以对数组先进行排序,然后就可以很顺利找到重复数字了。【时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),涉及到排序算法,暂时没有实现】。另外可以利用哈希表查找迅速的特点,建立一个哈希表,因为数组可以作为哈希表特殊情况处理,下标作为key,数值作为value,所以首先根据这种思路设计以下方案。时间复杂度为 O ( n ) O(n) O(n),但是因为存储哈希数组需要空间,所以额外引入了 O ( n ) O(n) O(n)空间复杂度。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers == nullptr || length <= 0)
return false;
for(int i=0;i<length;i++)
{
if(numbers[i]<0 || numbers[i] > length-1)
return false;
}
int temp[20];
memset(temp,-1,sizeof(temp));
for(int i=0;i<length;++i)
{
if(temp[numbers[i]] == -1)
{
temp[numbers[i]] = numbers[i];
}else{
*duplication = numbers[i];
return true;
}
}
return false;
}
};
思路:因此希望找到空间复杂度为 O ( 1 ) O(1) O(1)的方法。题目中数字都在0~ n − 1 n-1 n−1范围内,如果数组中没有重复数字,那么当数组排序之后数字 i i i将出现在下标为 i i i位置。但是因为数组中有重复数字,所以有可能有的位置将出现多个数字,但是有的位置没有数字。
利用这个特点,对整个数组进行重排。从头到尾依次扫描整个数组的值,当扫描到第 i i i个位置时,如果该位置的值 m m m不等于i,就将这个数字与 m m m位置的数字进行比较,如果相等就说明有数字出现了重复,因为位置 i i i,和位置 m m m的数字相同,如果不相等就将两个数字交换,使值为 m m m的数放到他应该在位置上,如果等于 i i i就继续扫描下一个位置。接下来重复这个过程。
下面以一个具体的例子来说明整个方法,该方法的看似两层循环,实际上每个数字最多只需要交换两次就能找到他的位置,因此总的时间复杂度为 O ( n ) O(n) O(n),没有引入空间复杂度。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers == nullptr || length <=0)
return false;
for(int i=0;i<length;i++)
{
if(numbers[i]<0 || numbers[i]>length-1)
return false;
}
for(int i=0;i<length;i++)
{
while(numbers[i] != i)
{
if(numbers[i] == numbers[numbers[i]])
{
*duplication = numbers[i];
return true;
}
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
};
思路:上面的解法虽然没有引入空间复杂度,但是直接遍历数组对数字进行排序,改变了原来的数组,有没有一种方法,不改变数组也不引入空间复杂度。
根据题目来说,长度为n的数组数字都在0-n-1之间,如果在数组的某一个区间内,例如数字0-n/2之间数字的总个数超过了n/2+1,那么我们可以判定这个区间内一定有重复数字。根据这个特点,我们将数组根据中间值一直二分直到找到重复的数字。以下图为例说明算法原理:
begin从1开始,与上述题目不太一样,要求数组中数字是正整数
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers == nullptr || length <= 0)
return false;
for(int i=0;i<length;i++)
{
if(numbers[i]<0 || numbers[i]>length -1)
return false;
}
int begin = 1;
int end = length-1;
while(end >= begin)
{
int middle = ((end-begin)>>1) + begin;
int count = countRange(numbers,length,begin,middle);
if(end == begin)
{
if(count>1)
{
*duplication = begin;
return true;
}
else
break;
}
if(count>(middle-begin+1))
end = middle;
else
begin = middle+1;
}
return false;
}
int countRange(const int * numbers,int length,int begin,int end)
{
if(numbers == nullptr)
return 0;
int count=0;
for(int i=0;i<length;i++)
{
if(numbers[i]>=begin && numbers[i]<=end)
count++;
}
return count;
}
};
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if (pHead == nullptr)
return nullptr;
if (pHead != nullptr && pHead->next == nullptr)
return pHead;
ListNode* current;
if ( pHead->next->val == pHead->val){
current = pHead->next->next;
while (current != nullptr && current->val == pHead->val)
current = current->next;
return deleteDuplication(current);
}
else {
current = pHead->next;
pHead->next = deleteDuplication(current);
return pHead;
}
}
};
非递归方法,使用三个指针指示链表相邻数据之间的关系,为了防止头指针被删除,剑指offer中将形参设置为指向头指针的指针,这样可以即使头指针被删除仍然能找到其地址进行数值移动,但是在线编程给定了函数接口,形参就是头指针,如果头指针被删除,就找不到开始指针的地址了。参考答案,找到了该问题的解决方法,新建一个节点,防止头指针消失。ListNode* newHead=new ListNode(-1); newHead->next=pHead; 虽然是一个小问题,但是解决掉还是觉得很惊喜。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == nullptr)
return nullptr;
if(pHead != nullptr && pHead->next == nullptr)
return pHead;
ListNode* newHead=new ListNode(-1);
newHead->next=pHead;
ListNode* preNode = newHead;
ListNode* pNode = pHead;
while(pNode != nullptr)
{
ListNode* pNext = pNode->next;
bool deleteNode = false;
if(pNext != nullptr && pNode->val == pNext->val)
deleteNode = true;
if(!deleteNode)
{
preNode = pNode;
pNode = pNode->next;
}else{
int value = pNode->val;
ListNode* delNode = pNode;
while(delNode != nullptr && delNode->val == value)
{
pNext = delNode->next;
delete delNode;
delNode = nullptr;
delNode = pNext;
}
preNode->next = pNext;
pNode = pNext;
}
}
return newHead->next;
}
};
题目:输入一个整数 n n n,求 1 − n 1-n 1−n这 n n n个整数的十进制表示中1出现的次数,例如,输入12, 1 − 12 1-12 1−12这些整数中包含1的数字有1,10,11,12,一共出现了5次。
最直观的方法,就是累加1-n中每个整数1出现的次数。可以每次通过对10求余数判断整数的个位数字是不是1如果这个数字大于10,则除以10之后再判断个位数字是不是1. 如果输入数字是n,根据2进制计算知道这个数一共有 l o g n logn logn位,需要判断每一位是不是1,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。计算量还是比较大的。
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n <= 0 )
return 0;
int count = 0;
for(int i=1 ;i<= n;i++)
{
count = count + Count(i);
}
return count;
}
int Count(int n)
{
int count =0;
while(n)
{
if(n%10 ==1)
count++;
n=n/10;
}
return count;
}
};
这种解法的时间复杂度应该是 O ( l o g n ) O(logn) O(logn),很简单,思路清晰。
主要思路:设定整数点(如1、10、100等等)作为位置点 i i i(对应 n n n的各位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析
根据设定的整数位置,对 n n n进行分割,分为两部分,高位 n / i n/i n/i,低位 n % i n\%i n%i
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
//每次去掉最高位递归
if(n <= 0)
return 0;
int count = 0;
int i = 1;
// i表示该位上为1的所有可能情况
for(i = 1;i <= n;i=i*10)
{
int a = n/i,b = n%i;
count = count+(a+8)/10*i+(a%10==1)*(b+1);
}
return count;
}
};
剑指offer提供的第二种方法是观察数字的规律,利用递归的方法解题。
如上图所示,可以看出从最低开始,其实是一个递归过程,如果求出来最低的位数,后面的可以直接根据前面的结果进行计算。【精髓,每次去掉最高位进行递归】与剑指offer思路类似,Java实现,我没有进行修改。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
if(n<0){
return 0;
}
String str= Integer.toString(n);
int result = getNumberOf1(str, 0);
return result;
}
public static int getNumberOf1(String str,int index){
int length = str.length()-index;
if(length==1 && str.charAt(index)=='0'){
return 0;
}
if(length==1){
return 1;
}
//计算最高位的1
int first = str.charAt(index)-'0';
int result = 0;
if(first>1){
result += exp(length-1);
}else if(first==1){
result += 1 + Integer.parseInt(str.substring(index+1));
}
//计算除了最高位的其他位
result += first *(length-1)*exp(length-2);
//计算比如2345中0---345中1的个数进行递归
result += getNumberOf1(str, index+1);
return result;
}
public static int exp(int n){
int result =1;
while(n>=1){
result*=10;
n--;
}
return result;
}
}