《剑指offer》学习笔记

(I) 扎实的基础知识:

1.编程语言(C++):概念理解

                            分析代码运行结果

                            写代码定义一个类或类的成员函数(构造函数,析构函数,运算符重载)

                            《Effective C++》,《C++ primer》

2.数据结构(二叉树和链表)

(1)数组:内存连续,根据下标O(1)读写,时间效率高,可实现简单的哈希表,预先分配内存,总有空闲区域,空间效率低(动态数组vector)。数组名为指向数组第一个元素的指针,可通过指针访问数组元素(对数组名直接取sizeof求得的是数组大小,若用数组名当参数传递或定义指针指向数组,则sizeof结果为4个字节,因为32位系统,任意指针均为4个字节

(2)字符串:内存连续的若干字符,以‘\0’结尾故需要一个额外字符开销,注意不要越界,需要遍历找到字符串长度,当几个指针幅值给相同的常量字符串时,实际指向相同的内存地址。

(3)链表:动态数据结构,创建时无需知道长度,插入时重新分配内存。然后调整指针链接,每添加一个结点分配一次内存,无闲置内存,空间效率比数组高。插入结点:

void AddToTail(ListNode ** pHead, int value)
{
	ListNode *pNew = new ListNode ();
	pNew -> val = value;
	pNwa -> next = NULL;
	if (*pHead == NULL)
		pHead = pNew;
	else
	{
		ListNode *pNode = *pHead;
		while (pNode -> next != NULL)
			pNode = pNode -> next;
		pNode -> nexrt = pNew;
	}
}


注意pHead为指向指针的指针,当往空链表中插入时,插入的结点即为头指针,由于会改变头指针,故设为指向指针的指针,否则出了这个函数仍为空指针

找到第i个结点需要从头遍历,时间效率为O(n),找到某值结点并删除:

void AddToTail(ListNode ** pHead, int value)
{
	ListNode *pNew = new ListNode ();
	pNew -> val = value;
	pNwa -> next = NULL;
	if (*pHead == NULL)
		pHead = pNew;
	else
	{
		ListNode *pNode = *pHead;
		while (pNode -> next != NULL)
			pNode = pNode -> next;
		pNode -> nexrt = pNew;
	}
}


其他:  把末尾节点指针指向头结点,环形链表

              每个结点既有指向下一结点也有指向前一结点,双向链表

              每个结点既有指向下一结点也有指向任意结点,复杂链表

另外有栈相关操作(先入后出):

stack<ListNode *> node;

                                   Node.push(pNode);

                                   pNode= node.top();

                                   node.pop();

(4)树:除根结点外每个结点只有一个父结点;除叶结点外所有结点有一个或多个子结点,父结点与子结点间用指针链接。常见的为二叉树,每个结点最多两个子结点,最重要的操作为遍历(前序遍历,中序遍历,后序遍历各有递归和循环两种实现方法需了如指掌),另外还有宽度遍历。

补充:

//***前序遍历(递归版):
vector<int> preorderTraversal(TreeNode* root){
    vector<int> ret;
    PreOrder(root,ret);
    return ret;
}
void PreOrder(TreeNode* root,vector<int> &ret){
    if(root==NULL) return;
    ret.push_back(root->val);//存储根节点
    PreOrder(root->left,ret);//访问左子树
    PreOrder(root->right,ret);//访问右子树
}


//***前序遍历(非递归版):
vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        if (root == NULL)
            return ret;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* tp = st.top();
            st.pop();
            ret.push_back(tp -> val);
            if(tp -> right !=NULL)
                st.push(tp -> right);
            if(tp -> left !=NULL)
                st.push(tp -> left);
        }

        return ret;
} 


//***中序遍历(递归版):
vector<int> inorderTraversal(TreeNode* root){
    vector<int> ret;
    inOrder(root,ret);
    return ret;
}
void inOrder(TreeNode* root,vector<int> &ret){
if(root==NULL) return;
inOrder(root->left,ret);//访问左子树
    ret.push_back(root->val);//存储根节点
    inOrder(root->right,ret);//访问右子树
}
//***中序遍历(非递归版):
vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        TreeNode* p = root;
        stack<TreeNode*> st;
        while(!st.empty()||p!=NULL)
        {//仍有左子树则继续找左子树
            if(p)
            {
                st.push(p);
                p = p ->left;
            }
            else
            {//已无左子树
                p = st.top();
                st.pop();
                ret.push_back(p -> val); //输出栈顶,实为当前根结点
                if (p -> right) //虽无左子树,但该根结点可能还有右子树,若有则入栈
                {
                    st.push(p -> right);
                    p = p -> right ->left; //判断右子树是否有左子树
                }
                else 
                    p=NULL;  //没有右子树则赋空值,让if(p)判断弹出下一个结点  
            }
        }
        return ret;
}


//***后序遍历(递归版):
vector<int> postorderTraversal(TreeNode* root){
    vector<int> ret;
    postOrder(root,ret);
    return ret;
}
void postOrder(TreeNode* root,vector<int> &ret){
if(root==NULL) return;
postOrder(root->left,ret);//访问左子树
postOrder(root->right,ret);//访问右子树
    ret.push_back(root->val);//存储根节点
}
//***后序遍历(非递归版):将先序遍历的左右子树顺序反过来后逆序输出
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        if (root == NULL)
            return ret;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* tp = st.top();
            st.pop();
            ret.push_back(tp -> val);
            if(tp -> left !=NULL)
                st.push(tp -> left);
            if(tp -> right !=NULL)
                st.push(tp -> right);
        }
        reverse(ret.begin(), ret.end());
        return ret;
}


//***层次遍历
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> ret;
    if(root==NULL) return ret;
    queue<TreeNode*> que;
    que.push(root);
    while(!que.empty())
    {
        vector<int> temp;
        queue<TreeNode*> tmpQue;//存储下一层需要访问的节点
        while(!que.empty())//从左到右依次访问本层
        {
            TreeNode* tempNode = que.front();
            que.pop();
            temp.push_back(tempNode->val);
            if(tempNode->left!=NULL) tmpQue.push(tempNode->left);//左子树压入队列
            if(tempNode->right!=NULL) tmpQue.push(tempNode->right);//右子树压入队列
        }
        ret.push_back(temp);
        que=tmpQue;//访问下一层
    }
    return ret;
}

特例:二叉搜索树,左子树结点总是小于等于根结点而右子结点总是大于等于根结点,可在O(logn)的时间内找到一个结点。

特例:堆分为最大堆和最小堆指的是根结点最大或最小,快速找到最值可用堆解决。

特例:红黑树,根为红和黑两种,通过规则确保从根结点到叶结点的最长路径不超过最短路径的二倍,set,multiset,map,multimap均基于红黑树实现。

(5)栈和队列:栈先入后出,最后push的最先pop;队列先入先出,注意二者的相互联系

3.算法(二分查找,归并排序和快速排序,动态规划和贪婪算法)。

(1)查找

查找:顺序查找,二分查找,哈希表查找,二叉排序树查找

在排序的数组或部分排序的数组中查找---二分查找(信手拈来)

int binarySerach(int[] array, int key) 
{
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
while (left <= right) 
{
        int mid = (left + right) / 2;
        if (array[mid] == key) 
            return mid;
        else if (array[mid] < key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    return -1;
}


哈希表查找:O(1)的时间效率,但需要额外的空间来实现哈希表

(2)排序

比较各种排序算法空间消耗,平均和最坏时间复杂度(烂熟于胸)

快排的代码:

int Partition(intdata[], int lentgth, int start, int end)
{
     if (data == NULL || length <= 0 || start< 0 || end >= length)
            throw new exception(“ InvalidParameters”);
     int index = RandomInRange(start, end);
     Swap(&data[index], &data[end]);
     int small = start – 1;
     for(index = start; index < end; ++index)
     {
            if(data[index]< data[end])
            {
                   ++ small;
                   if (small != index)
                          Swap (&data[index],&data[small]
            }
     }
++ small;
Swap(&data[small],&data[end]);
return small;
}
      
       VoidQuickSort ( int data[], int length, int start, int end)
{
       if (start == end)
              return;
       int index = Partition(data, lenth, start,end);
       if (index > start)
                     QuickSort (data, length, start, index – 1);
if (index < end)
                     QuickSort (data, length, index + 1, end);
}

(3)递归和循环

若无特殊要求,优先使用递归算法。

递归简洁,但时间和空间消耗大(调用栈溢出),且很多计算是重复的,性能不好

(4)位运算:与,或,异或,左移(右侧补零),右移(有符号数左移补符号位,某则补零)。

***一个整数减一后再与原来做位与运算,相当于把二进制最右边一个1变为0

 

(II)高质量的代码:完整性,完成基本功能,边界条件,内存覆盖,特殊输入(空指针,0,空字符串),错误处理(输入不合法),笔试时可能需要考虑可重复输入

(III)分析思路清晰:  举例具体化

                            画图形象化(数据结构链表等)

                            分解简单化(递归—分治法和动态规划)

 

(IV)优化时间和空间效率:

 

(V)学习沟通能力:观点明确,逻辑清晰,团队合作意识,最近看的书和学到的技术,对新概念的理解能力,知识迁移能力,抽象建模能力,发散思维能力

猜你喜欢

转载自blog.csdn.net/qy724728631/article/details/78609069